P3158 [CQOI2011]放棋子 计数DP+组合数学

题意:

在一个 (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;
}

原文地址:https://www.cnblogs.com/youth518/p/13984082.html