【BZOJ3294】放棋子(动态规划,容斥,组合数学)
题面
题解
如果某一行某一列被某一种颜色给占了,那么在考虑其他行的时候可以直接把这些行和这些列给丢掉。
那么我们就可以写出一个(dp)
设(f[i][r][c])表示考虑了前(i)种颜色,还剩下(r)行(c)列没被染色。
那么转移的时候枚举一下当前颜色染了(a)行(b)列转移就好了。
但是问题来了,怎么计算用(K)个棋子恰好覆盖(a)行(b)列的方案数呢?
恰好很不好算,那么我们换一下,至多覆盖了(a)行(b)列的方案数。
那么这个很容易算出来是(C_{ab}^{K})。
那么我们可以容斥计算恰好覆盖了(a)行(b)列的方案数。
我们在计算(a,b)的时候就已经可以算出来恰好覆盖了(l,l<a)行(r,r<b)列的方案数,
那么直接拿总数减去不合法的就好了。
接下来就是一个很简单的(dp)了,稍微用组合数算一下即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define MOD 1000000009
#define MAX 35
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int n,m,c,ans;
int f[MAX][MAX][MAX],a[MAX];
int jc[MAX*MAX],jv[MAX*MAX],inv[MAX*MAX];
int g[MAX][MAX][MAX];
int C(int n,int m){if(m>n)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int main()
{
n=read();m=read();c=read();
for(int i=1;i<=c;++i)a[i]=read();
jc[0]=inv[0]=inv[1]=jv[0]=1;
for(int i=1;i<=n*m;++i)jc[i]=1ll*jc[i-1]*i%MOD;
for(int i=2;i<=n*m;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n*m;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
for(int i=1;i<=c;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=m;++k)
{
if(j*k<a[i])continue;
g[i][j][k]=C(j*k,a[i]);
for(int l=1;l<=j;++l)
for(int r=1;r<=k;++r)
if(l!=j||r!=k)add(g[i][j][k],MOD-1ll*C(j,l)*C(k,r)%MOD*g[i][l][r]%MOD);
}
f[0][0][0]=1;
for(int i=1;i<=c;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=m;++k)
for(int a=1;a<=j;++a)
for(int b=1;b<=k;++b)
add(f[i][j][k],1ll*g[i][a][b]*f[i-1][j-a][k-b]%MOD*C(n-j+a,a)%MOD*(C(m-k+b,b))%MOD);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)add(ans,f[c][i][j]);
printf("%d
",ans);
return 0;
}