题意:
在一个 (m) 行 (n) 列的棋盘里放 (k) 种彩色的棋子,使得每个格子最多放一个棋子,且不同颜色的棋子不能在同一行或者同一列,有多少种方法?
范围&性质 (1le n,mle 30,1le kle 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 imes j}^k-sum_{l=1}^isum_{r=1}^jg[l][r][k] imes C_i^l imes C_j^r
]
总方案数等于能放的 (i imes 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
",ans);
}
}
int main()
{
zzc::work();
return 0;
}