题意
有 \(c\) 种棋子,每种棋子都有相应的个数,要把全部棋子放入棋盘中,使得每一行和每一列没有颜色相同的棋子,求方案数。
思路
从行和列的角度显然不好处理,所以我们可以先从颜色的种类入手。
设计 \(f_{c,i,j}\) 表示前 \(c\) 种颜色,已经有 \(i\) 行,\(j\) 列被占领的方案数。
枚举放第 \(c\) 种棋子要占据 \(x\) 行,\(y\) 列,则有转移:
\[f_{c,i,j} = f_{c-1,i-x,j-y}*g_{x,y,a_c}
\]
\(g_{x,y,a_c}\) 表示将 \(a_c\) 个数填入 \(x*y\) 的表格中使得每一行每一列都有数的方案数。
考虑如何转移(难点),同样采用总 - 非法的策略:
- 总:是显然的,即 \(\binom{i*j}{k}\)
- 非法:即有行或列是空出来的,枚举空出来多少行列,则有 \(g_{i,j,k} = \binom{i}{x} \binom{j}{y} g_{x,y,k}\)。
上下相减就是答案。
但是要注意非法的时间复杂度是 \(O(n^6)\),考虑把 \(k\) 提前,换成 \(a_c\),算完 \(g\) 就直接算 \(f\),这样时间复杂度就是 \(O(n^5) 了\)。
code
#include<bits/stdc++.h>
#define ll long longusing namespace std;const ll N = 31,M = 11,mod = 1e9+9;int n,m,p,cnt;
int a[M];
ll f[M][N][N];
ll dp[N][N];
ll C[N*N][N*N],fac[N*N];void init() {C[0][0]=1,fac[0]=1;for(int i=1;i<=900;i++){C[i][0]=1,fac[i]=fac[i-1]*i%mod;for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;}
}void add(ll &x,ll y){(x+=y)%=mod,(x+=mod)%=mod;
}
int main() {cin>>n>>m>>p;init();for(int i=1;i<=p;i++){ll x;cin>>x;if(x)a[++cnt]=x;}p=cnt;f[0][0][0]=1;for(int k=1;k<=p;k++){memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){if(i*j<a[k] || i>a[k] || j>a[k])continue;dp[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)add(dp[i][j],-dp[x][y]*C[i][x]%mod*C[j][y]%mod);
// cout<<k<<" "<<i<<" "<<j<<" "<<dp[i][j]<<"\n";}//1 2 2for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int x=1;x<=min(i,a[k]);x++){for(int y=1;y<=j;y++){if(x*y<a[k])continue;add(f[k][i][j],f[k-1][i-x][j-y]*C[n-i+x][x]%mod*C[m-j+y][y]%mod*dp[x][y]%mod);}}// cout<<c<<" "<<i<<" "<<j<<" "<<f[c][i][j]<<"\n";}}}ll ans=0;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)ans+=f[p][i][j],ans%=mod;cout<<ans;return 0;
}