P3158 [CQOI2011]放棋子 计数DP+组合数学
题意:
在一个 \(m\) 行 \(n\) 列的棋盘里放 \(k\) 种彩色的棋子,使得每个格子最多放一个棋子,且不同颜色的棋子不能在同一行或者同一列,有多少种方法?
范围&性质 \(1\le n,m\le 30,1\le k\le 10\)
分析:
- 暴力
枚举每行每列放什么,复杂度 \(O(k^{n+m})\)
- 正解
按照颜色来考虑贡献,将每一种颜色带来的贡献单独考虑,同时我们发现方案数与行列的顺序无关,只是组合数的影响,所以我们在设 DP 状态时只需要考虑行列的个数
记 \(f[i][j][k]\) 表示前 \(k\) 种颜色覆盖了 \(i\) 行 \(j\) 列的方案,转移时需要枚举第 \(k+1\) 种颜色会覆盖新的行和列的个数,但是我们发现第 \(k+1\) 种颜色的贡献似乎不好直接表示出来
那么我们专门来计算每个颜色的方案数,记 \(g[i][j][k]\) 表示用 \(k\) 个同样颜色的棋子,覆盖 \(i\) 行 \(j\) 列的方案,可以用容斥的方法来计算,即总方案减去不合法方案数
\[g[i][j][k]=C_{i\times j}^k-\sum_{l=1}^i\sum_{r=1}^jg[l][r][k]\times C_i^l\times C_j^r
\]
总方案数等于能放的 \(i\times j\) 里面选 \(k\) 个,不合法的方案等价于从当前 \(i\) 行里面枚举重复了 \(l\) 行 ,当前 \(j\) 列里面枚举重复了 \(r\) 列
总的转移式就是 \(f[i][j][k]=f[l][r][k-1]+g[i-l][j-r][a[k]]\)
答案就是 \(\sum f[i][j][k]\) ,因为存在空出几行没有被覆盖的情况,所以需要枚举覆盖的行和列,复杂度\(O(n^2m^2k)\)
代码:
#include<bits/stdc++.h>
using namespace std;
namespace zzc
{
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 35;
const int maxm = 905;
const int mod = 1e9+9;
int c[maxm][maxm];
int f[maxn][maxn][maxm],g[maxn][maxn];
int n,m,k;
long long ans=0;
void init()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<=900;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
}
void work()
{
int a;
init();
n=read();m=read();k=read();
f[0][0][0]=1;
for(int i=1;i<=k;i++)
{
a=read();
memset(g,0,sizeof(g));
for(int x=1;x<=n;x++)
{
for(int y=1;y<=m;y++)
{
if(x*y>=a)
{
g[x][y]=c[x*y][a];
for(int l=1;l<=x;l++)
{
for(int r=1;r<=y;r++)
{
if(l<x||r<y) g[x][y]=(g[x][y]-(long long)g[l][r]*c[x][l]%mod*c[y][r]%mod+mod)%mod;
}
}
}
}
}
for(int x=1;x<=n;x++)
{
for(int y=1;y<=m;y++)
{
for(int l=0;l<=x;l++)
{
for(int r=0;r<=y;r++)
{
int tmpx=x-l;
int tmpy=y-r;
if(tmpx*tmpy>=a)
{
f[x][y][i]=(f[x][y][i]+(long long)f[l][r][i-1]*g[tmpx][tmpy]%mod*c[n-l][tmpx]%mod*c[m-r][tmpy]%mod)%mod;
}
}
}
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
ans=(ans+f[i][j][k])%mod;
}
}
printf("%lld\n",ans);
}
}
int main()
{
zzc::work();
return 0;
}