考慮$DP[n][k]$代表$n*n$的正方形分成$k$群的答案
於是乎我們得到以下邊界條件:
$if\ n<2k$,$DP[n][k]=0$
$if\ n=2k$,$DP[n][k]=\prod_{i=1}^{k}(2i-1)\binom{2i}{2}$
其中,$DP[2k][k]$可以用$DP[2(k-1)][k-1]*(2k-1)*\binom{2k}{2}$遞推出來(不過直接算也是OK,反正$k$只到50而已嘛XD)
再來考慮$n>2k$時的情況怎麼從$DP[n-1][k]$算出來
可以發現,當我們加入新的一行和一列後,固定新列在最底下,新行則有$n$個插入位置,假設插入後的位置是$x_{0}$好了
那麼,我們可以在$(x_{0},n)$放個守衛,再隨意找一個$x_{1}\neq x_{0}$,在$(x_{1},n)$再放個守衛,這時第$x_{1}$行變成了三個守衛,分別在第$y_{0}$、$y_{1}$、$n$列
怎麼辦呢?
只要從$(x_{1},y_{0})$、$(x_{1},y_{1})$中選一個(假設選$y_{i}$)守衛解雇,然後在$(x_{0},y_{i})$這個位置新增一個守衛就好了(還記的第$x_{0}$行是新行只有一個守衛吧XD)
上述計算方式會有重複,也就是第$x_{0}$行和第$x_{1}$行是對稱的,因此要除以2
我們得到$DP[n][k]=DP[n-1][k]*\frac{2n(n-1)}{2}$
可是...會發現,第$n$行的那群守衛所屬的守衛群大小(定義守衛群大小為其覆蓋的行數或列數)一定$>2$(因為是和另一個守衛群合併)
那第$n$行所屬的守衛群大小$=2$的情況怎麼辦?
可以從$DP[n-2][k-1]$來轉移!
這次,我們一次加入兩行兩列,這些行和列自成一個大小為2的守衛群
一列要固定在第$n$行,另一列則有$n-1$種選擇
兩行有$\frac{n(n-1)}{2}$種排列方式
因此當$k>1$時,$DP[n][k]$還得再加上$DP[n-2][k-1]*(n-1)*\binom{2i}{2}$
使用記憶化搜索實現應該會比較方便
時間複雜度:$O(NK)$
code:
#include<cstdio> #include<cassert> using namespace std; typedef long long LL; const LL MOD=1000000007LL; LL DP[100001][51]; LL GetDP(const int n,const int k) { if(n<k*2)return 0LL; assert(2<=n&&n<=100000&&1<=k&&k<=50); LL &ans=DP[n][k]; if(ans!=-1)return ans; ans=0LL; (ans+=GetDP(n-1,k)*(((LL)n*(n-1LL)/2LL)*2LL%MOD))%=MOD; if(k>1)(ans+=GetDP(n-2,k-1)*(((LL)n*(n-1LL)/2LL)%MOD*(n-1LL)%MOD))%=MOD; return ans; } int N,K; int main() { // freopen("in.txt","r",stdin); for(int i=2;i<=100000;i++)for(int j=1;j<=50;j++)DP[i][j]=-1LL; DP[2][1]=1; // DP[4][2]=1*3*(4*3)/2*(2*1)/2; //DP[2k][k]=1*3*...*2k-1*k!/2^(k/2) for(int k=2;k<=50;k++)DP[2*k][k]=(DP[2*(k-1)][k-1]*(2LL*k-1LL)%MOD*((2LL*k*(2LL*k-1LL))/2LL)%MOD); // for(int i=1;i<=5;i++)printf("%lld ",DP[i*2][i]);puts(""); int testcount;scanf("%d",&testcount); while(testcount--) { scanf("%d%d",&N,&K); static int kase=1; printf("Case %d: %lld\n",kase++,GetDP(N,K)); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。