BZOJ3294: [Cqoi2011]放棋子
Description
Input
输入第一行为两个整数n, m, c,即行数、列数和棋子的颜色数。第二行包含c个正整数,即每个颜色的棋子数。所有颜色的棋子总数保证不超过nm。
Output
输出仅一行,即方案总数除以 1,000,000,009的余数。
Sample Input
4 2 2
3 1
3 1
Sample Output
8
HINT
N,M<=30 C<=10 总棋子数<=250
我们发现因为不能同行同列颜色不同的格子,所以我们相当于是将整张棋盘的行列分给这C种棋子。
这样我们设g[n][m][k]表示前k种棋子放完,占据了n行m列的方案数。g[n][m][k]=sum(g[n-x][n-y][k-1]*C(x,n)*C(y,n)*f[x][y][k])。
其中f[x][y][k]表示第i种棋子刚好放在了x行y列棋盘的方案数,要求每行每列都至少有一个棋子。
不难发现f数组可以用容斥原理来计算,枚举有多少行列没放棋子,转移方程也很简单。
#include<cstdio> #include<cctype> #include<queue> #include<cmath> #include<cstring> #include<algorithm> #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define ren for(int i=first[x];i;i=next[i]) using namespace std; inline int read() { int x=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } typedef long long ll; const int maxn=35; const int maxm=255; const int mod=1000000009; ll C[1010][1010],f[maxn][maxn][11],g[maxn][maxn][11]; int n,m,c,A[maxn]; void init() { C[0][0]=1; rep(i,1,1000) { C[i][0]=C[i][i]=1; rep(j,1,i-1) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } } int main() { n=read();m=read();c=read(); rep(i,1,c) A[i]=read(); init(); rep(i,1,n) rep(j,1,m) rep(k,1,c) if(i<=A[k]&&j<=A[k]&&i*j>=A[k]){ f[i][j][k]=C[i*j][A[k]];ll& ans=f[i][j][k]; rep(i1,1,i) rep(j1,1,j) if(i1!=i||j1!=j) (ans-=(f[i1][j1][k]*C[i][i1]%mod)*C[j][j1])%=mod; ans=(ans%mod+mod)%mod; } g[0][0][0]=1; rep(i,1,n) rep(j,1,m) rep(k,1,c) { ll& ans=g[i][j][k]; rep(i1,1,i) rep(j1,1,j) (ans+=(((f[i1][j1][k]*C[i][i1]%mod)*C[j][j1])%mod)*g[i-i1][j-j1][k-1])%=mod; } ll ans=0; rep(i,1,n) rep(j,1,m) (ans+=C[n][i]*C[m][j]%mod*g[i][j][c]%mod)%=mod; printf("%lld\n",ans); return 0; }