放旗子[题解]

放旗子

这是一道计数 \(DP\) 题,不同颜色的棋子不能在同一行同一列,否则就不合法,所以每种颜色的棋子摆放的方案数是相对独立的。

题目大意

小虎刚刚上了幼儿园,老师让他做一个家庭作业:首先画 \(3\) 行格子,第一行有三个格子,第二行有 \(2\) 个格子,第三行有 \(1\) 个格子。每行的格子从左到右可以放棋子,但要求除第一行外,每行放的棋子数不能超过上一行的棋子。第一行的棋子数不能为 \(0\) ,但剩下行可以为空。玩了一会儿,小虎对哥哥大虎说:”这个作业有很多种摆放法,我想找到,但我不知道有多少种方案,你能帮助我吗?”

大虎是学校信息学集训队的,立刻想到用计算机来解决这个问题,并很快有了解答:\(13\)

第二天他把问题拿到学校,并说如果第一行有 \(N\) 个格子,第二行有 \(N−1\) 个格子,……,第 \(N\) 行有 \(1\) 个格子,怎么办?现在请你一块来帮助他解决这个难题。

分析

预设状态

我们设数组 \(f[i][j][k]\) 表示\(k\) 种颜色占领了任意 \(i\)\(j\) 列的方案数。

设数组 \(g[i][j][k]\) 表示任意k枚同色棋子放任意 \(i\)\(j\) 列的方案数。

\[f[i][j][k]=\sum _{l=0}^{i-1}\sum _{r=0}^{j-1}f[l][r][k-1]\times g[i-l][j-r][a[k]]\times C_{n-l}^{i-l}\times C_{m-r}^{j-r} ((i-l)\times (j-r)≥a[k]) \]

理解公式

这个部分对上述公式进行解释,大家可以自己先思考一下为什么。

假设我们当前计算的是第 \(k\) 种颜色,那么\(k-1\) 种颜色所占领的行列肯定会包含在 \(i\)\(j\),但又不能超过 \(i\)\(j\) ,因为第 \(k\) 种颜色至少会占领其中一行,而 \(l\)\(r\)\(0\) 开始枚举是因为当计算第 \(1\) 种颜色时,前面的颜色没有占领任何一行一列。所以,我们可以在这个范围内枚举前 \(k-1\) 种颜色占领 \(l\)\(r\) 列的情况

接下来,我们能够发现,既然\(k-1\)棋子占领l行r列,\(k\)棋子占领 \(i\)\(j\) 列,那\(k\)棋子就会占领 \(i-l\)\(j-r\) 列,所以 \(g[i-l][j-r][a[k]]\) 考虑的是当前第 \(k\) 种棋子放置的方案数

而后面的两个组合数其实就是当前 \(k-1\) 种棋子已经占领了 \(l\)\(r\) 列时\(k\) 种棋子从剩下的行列中占领哪些行列的方案数,这些东西结合在一起,就求得了我们的 \(f[i][j][k]\)

最后为什么会有这个范围,原因在与棋子是必须要放完的,如果你枚举的行列所拥有的格子数不足该种颜色棋子的总数量,说明这种方法不可行。

求解各个部分

理清楚了为什么,接下来的问题就是求解。

其实 \(g[i][j][k]\) 就是总方案数-不合法的方案数,什么意思呢?就是将 \(k\) 个棋子随意分入 \(i\)\(j\) 列种也许会分到前面已经被占领的行列,我们要将这些方案数减去。总方案数很好理解,从能够分的总格子数中选出 \(k\) 个各自来放,就是一个组合数。

而不合法的方案数也其实就是枚举 \(l\)\(r\) 列放 \(k\) 个棋子时的方案数再乘上\(i\) 行里面选 \(l\) 行的方案数\(j\) 行里面选 \(r\) 行的方案数,则 \(g\) 的求解公式即为:

\[g[i][j][k]= C_{i \times j}^{k} - \sum \limits_{l = 1}^i \sum \limits_{r = 1}^j g[l][r][k] \times C_i^l \times C_j^r(l < i || r < j, i \times j \ge k) \]

注意一下这里的范围其实就是使其不和 \(i\)\(j\) 列完全重合

scanf("%d",&a[k]);
		//在这里k=a[k] 
		memset(g,0,sizeof(g));
		//求解g数组 
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(i*j>=a[k]){
					g[i][j]=C[i*j][a[k]];
					for(int l=1;l<=i;l++)
						for(int r=1;r<=j;r++)
							if( l<i || r<j ) g[i][j]=((ll)g[i][j]-(ll)g[l][r]*C[i][l]%MOD*C[j][r]%MOD+MOD)%MOD;
				}
			}
		}

然后组合数由于数据很小,直接一波杨辉三角预处理即可,当然若是你不嫌麻烦,乘法逆元也未尝不可。

inline void intial()	//杨辉三角初始化组合数 
{
	for(int i=0;i<=N*N-1;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) 
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}

最后的答案也是呼之欲出,由于 \(n\)\(m\) 列不必放满,但是棋子得放完,答案就是:

\[\sum _{i=1}^n\sum _{j=1}^mf[i][j][c] \]

	//不一定每行每列都要有棋子,但是棋子得放完 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			ans=(ans+f[i][j][c])%MOD;

最后的最后

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35,K=15,MOD=1e9+9;
int n,m,c,a[K];
//不同颜色的棋子不能在同一行或者同一列,所以每种颜色的棋子摆放是相对独立的 
//状态f[i][j][k]表示用前k种颜色的棋子占领了任意i行j列的方案数 
ll ans,C[N*N][N*N],g[N][N],f[N][N][K]; 
inline void intial()	//杨辉三角初始化组合数 
{
	for(int i=0;i<=N*N-1;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) 
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}

int main()
{
	intial();
	scanf("%d%d%d",&n,&m,&c);
	//初始化f[0][0][0]=1 
	f[0][0][0]=1; 
	//另设一个状态g[i][j][k]表示任意k枚同色棋子占领任意i行j列的方案数 
	for(int k=1;k<=c;k++){
		scanf("%d",&a[k]);
		//在这里k=a[k] 
		memset(g,0,sizeof(g));
		//求解g数组 
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(i*j>=a[k]){
					g[i][j]=C[i*j][a[k]];
					for(int l=1;l<=i;l++)
						for(int r=1;r<=j;r++)
							if( l<i || r<j ) g[i][j]=((ll)g[i][j]-(ll)g[l][r]*C[i][l]%MOD*C[j][r]%MOD+MOD)%MOD;
				}
			}
		}
		//求解f数组 
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				for(int l=0;l<i;l++)
					for(int r=0;r<j;r++)
						if((i-l)*(j-r)>=a[k]) f[i][j][k]=(f[i][j][k]+(ll)f[l][r][k-1]*g[i-l][j-r]%MOD*C[n-l][i-l]%MOD*C[m-r][j-r]%MOD)%MOD;
	}
	//不一定每行每列都要有棋子,但是棋子得放完 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			ans=(ans+f[i][j][c])%MOD;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-03-01 19:54  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(103)  评论(0编辑  收藏  举报