BZOJ3294: [Cqoi2011]放棋子(计数Dp,组合数学)
解题思路:
发现一个性质,如果考虑一个合法的方案可以将行和列都压到一起,也就是说,在占用行数和列数一定的情况下,行列互换是不会影响答案的,那么考虑使用如下方程:
$f[i][j][k]$为占领了i行j列使用了前k种颜色,由于要求全部用完,不需要枚举放入多少,考虑一个一个来添加颜色。考虑添加第k种颜色:
因为第k种颜色一定是占据了新的一行一列,所以加入第k种颜色后的行数=加入之前的行数+第k种颜色占据的行数,列数同理。
设第k种颜色的棋子有a个,那么我们只需要知道用A种颜色占据i行j列的方案数,设为$g[i][j][A]$这个可以使用容斥
转移即为$g[i][j][A]=C_{i*j}^A-\limits\sum_{a=1}^{i}\limits\sum_{b=1}^{j}g[a][b][A]*C_i^a*C_j^b$发现和A毛关系没有就舍去了这一维。
所以最后的转移就是:
$f[i][j][k]=\limits\sum_{a=1}^{i}\limits\sum_{b=1}^{j}f[i-a][i-b][k-1]*g[a][b]*C_{n-i+a}^{a}*C_{m-j+b}^{b}$
答案就是i,j的累和。
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 typedef long long lnt; 5 const lnt mod=(lnt)(1e9+9); 6 lnt f[500][500][11]; 7 lnt g[500][500]; 8 int num[11]; 9 lnt C[1000][1000]; 10 int n,m,c; 11 void get_g(int k) 12 { 13 memset(g,0,sizeof(g)); 14 int A=num[k]; 15 for(int i=1;i<=n;i++) 16 { 17 for(int j=1;j<=m;j++) 18 { 19 if(i*j>=A) 20 { 21 g[i][j]=C[i*j][A]; 22 for(int a=1;a<=i;a++) 23 { 24 for(int b=1;b<=j;b++) 25 { 26 if(a==i&&b==j)continue; 27 (g[i][j]-=g[a][b]*C[i][a]%mod*C[j][b]%mod)%=mod; 28 } 29 } 30 } 31 } 32 } 33 return ; 34 } 35 void init(void) 36 { 37 C[0][0]=1; 38 for(int i=1;i<=900;i++) 39 { 40 C[i][0]=1; 41 for(int j=1;j<=i;j++) 42 { 43 C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; 44 } 45 } 46 return ; 47 } 48 int main() 49 { 50 scanf("%d%d%d",&n,&m,&c); 51 for(int i=1;i<=c;i++)scanf("%d",&num[i]); 52 init(); 53 f[0][0][0]=1; 54 for(int k=1;k<=c;k++) 55 { 56 get_g(k); 57 for(int i=1;i<=n;i++) 58 { 59 for(int j=1;j<=m;j++) 60 { 61 if(i*j<num[k])continue; 62 for(int a=i;a<=n;a++) 63 { 64 for(int b=j;b<=m;b++) 65 { 66 (f[a][b][k]+=f[a-i][b-j][k-1]*g[i][j]%mod*C[n-a+i][i]%mod*C[m-b+j][j]%mod)%=mod; 67 } 68 } 69 } 70 } 71 } 72 lnt ans(0); 73 for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)(ans+=f[i][j][c])%=mod; 74 printf("%lld\n",(ans%mod+mod)%mod); 75 return 0; 76 }