BZOJ3294 CQOI2011放棋子(动态规划)
可以看做棋子放在某个位置后该种颜色就占领了那一行一列。行列间彼此没有区别。
于是可以设f[i][j][k]表示前k种棋子占领了i行j列的方案数。转移时枚举第k种棋子占领几行几列。注意行列间是有序的,要乘上一个组合数。这里f[i][j][k]可以是在原棋盘选i行j列占领的方案数,也可以是占领i行j列棋盘的方案数,如果是第二种最后统计答案的时候还要乘上个组合数,转移略有不同但没有本质区别。我们还需要计算出k个棋子占领i行j列中的方案数才能转移。
考虑怎么求这个东西。设其为g[i][j][k]。不妨把行列尽量往左往上移,可以发现棋子只能放置在其重合区域,也就是一个i*j的棋盘。使得这里面每行每列都有棋子就可以了。
然而还是不太好算。考虑求存在某一行或某一列没有棋子的方案数,那么可以枚举其中有几行几列是空的转移。于是可得g[i][j][k]=C(i*j,k)-Σg[x][y][k]*C(i,x)*C(j,y) (x+y<i+j)。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } #define P 1000000009 #define N 31 #define K 11 int n,m,c,l,a[K],f[K][N][N],g[K][N][N],ans=0; int fac[N*N],inv[N*N],C[N*N][N*N]; void inc(int &x,int y){x+=y;if (x>=P) x-=P;} int main() { #ifndef ONLINE_JUDGE freopen("bzoj3294.in","r",stdin); freopen("bzoj3294.out","w",stdout); const char LL[]="%I64d"; #else const char LL[]="%lld"; #endif n=read(),m=read(),c=read(); for (int i=1;i<=c;i++) a[i]=read(); C[1][0]=C[1][1]=1; for (int i=2;i<=n*m;i++) { C[i][0]=C[i][i]=1; for (int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; } for (int k=1;k<=c;k++) for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (i*j>=a[k]) { g[k][i][j]=C[i*j][a[k]]; for (int x=1;x<=i;x++) for (int y=1;y<=j;y++) if (x<i||y<j) inc(g[k][i][j],P-1ll*C[i][x]*C[j][y]%P*g[k][x][y]%P); } f[0][0][0]=1; for (int k=1;k<=c;k++) for (int i=k;i<=n;i++) for (int j=k;j<=m;j++) if (i*j>=a[k]) for (int x=1;x<=i-k+1;x++) for (int y=1;y<=j-k+1;y++) inc(f[k][i][j],1ll*f[k-1][i-x][j-y]*g[k][x][y]%P*C[n-i+x][x]%P*C[m-j+y][y]%P); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) inc(ans,f[c][i][j]); cout<<ans; return 0; }