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 }

 

转载于:https://www.cnblogs.com/blog-Dr-J/p/10540108.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值