[CQOI2011]放棋子
在\(m\times n\)的棋盘中放入一些棋子,棋子由c种颜色组成,第i种颜色的棋子数有\(d_i\)个,要求每一行每一列只能有同种颜色的棋子或没有棋子,询问其方案数\(mod\ 10^9+7\),N,M<=30 C<=10 总棋子数<=250。
解
显然为错排问题,问题比较复杂,优先考虑递推方程,要表现出棋子的颜色种类,又要表现出行列,故设\(f[i][j][k]\)表示前k种颜色选了任意i行j列的方案数,又设\(g[i][j][k]\)表示k枚棋子放入i行j列网格图的方案数,注意到g不好划分和依据策略转移,于是考虑补集,不难有
\[g[i][j][k]=C_{ij}^k-\sum_{x=1}^i\sum_{y=1}^jg[x][y][k](x<i||y<j)
\]
注意到k是固定无须枚举,于是边算边求解,省得无用求解。
现在考虑f,不难知道它是与前k-1中颜色有关,而且还要知道这种颜色选了x,y列,并且还要保证装得下,于是有
\[f[i][j][k]=\sum_{x=1}^{i-1}\sum_{y=1}^{j-1}f[x][y][k-1]C_{m-x}^{i-x}C_{n-y}^{j-y}g[i-x][j-y][d[k]](xy\geq d[k])
\]
边界:\(f[0][0][0]=1\)
套用递推转移即可。
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
#define yyb 1000000009
using namespace std;
ll c[905][905],g[31][31],dp[31][31][11],ans;
int main(){
int n,m,color,i,j,k,l,r,gzy;
scanf("%d%d%d",&n,&m,&color);
for(i=0;c[i][0]=1,i<=900;++i)
for(j=1;j<=i;++j)c[i][j]=(c[i-1][j]+c[i-1][j-1])%yyb;
dp[0][0][0]=true;
for(k=1;k<=color;++k){
scanf("%d",&gzy);
for(i=1,memset(g,0,sizeof(g));i<=n;++i)
for(j=1;j<=m;++j){
g[i][j]=c[i*j][gzy];
for(l=1;l<=i;++l)
for(r=1;r<j;++r)
(g[i][j]-=g[l][r]*c[i][l]%yyb*c[j][r])%=yyb;
for(l=1;l<i;++l)(g[i][j]-=g[l][r]*c[i][l]%yyb*c[j][r])%=yyb;
(g[i][j]+=yyb)%=yyb;
}
for(i=1;i<=n;++i)
for(j=1;j<=m;++j)
for(l=0;l<i;++l)
for(r=0;r<j;++r)
(dp[i][j][k]+=dp[l][r][k-1]*c[n-l][i-l]%yyb*c[m-r][j-r]%yyb*g[i-l][j-r])%=yyb;
}for(l=1;l<=n;++l)for(r=1;r<=m;++r)(ans+=dp[l][r][color])%=yyb;printf("%lld",ans);
return 0;
}