CQOI2011 放棋子

CQOI2011 放棋子

很明显的dp题。算是组合计数类dp吧。

一个很妙的地方,一个棋子占据一行一列,我们去掉这一行一列,状态变为(n-1)* (m-1)的棋盘,这算是dp的一个子状态,递推的味道很浓。

我们考虑当前到了第k个颜色,f[i][j][k]表示前k种颜色占据了i行j列,这i和j只是个数,具体位置任意。那么f[i][j][k]=f[l][r][k-1]* g[i-l][j-r]* c[n-l][i-l]* c[m-r][j-r].
组合数很好理解,都说了ij是任意的。那g是啥呢???

g表示对于第k种颜色的c[k]个棋子,占据i行j列的方案数,这里的i和j也是任意的哦。
g全部的方案明显为C(i* j,c[k]),可是这么着放不一定占全了这i行j列,怎么办?减去不合法的呗。g[i][j]-=g[l][r]* C(i,l)* C(j,r).(l<i or r<j)同样也是递推的。

有人就疑问了,为什么有两次组合数呢?其实很好理解,推g数组时l和r是在一个确定的i和j里选的,也就是说这个组合数是l和r相对于i和j的小范围的,而f数组的组合数是相对整张棋盘的。

好了,本题到此结束。

code

#include<bits/stdc++.h>
#define int long long
#define m(x) memset(x,0,sizeof(x))
using namespace std;
const int mod=1000000009;
int n,m,C,ans,num[11],c[901][901],f[31][31][31],g[31][31];
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld%lld%lld",&n,&m,&C);}
namespace AYX
{	
	inline void work()
	{	for(int i=1;i<=C;i++)scanf("%lld",&num[i]);
		for(int i=0;i<=n*m;++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;
		}
		f[0][0][0]=1;
		for(int k=1;k<=C;++k)
		{	m(g);
			for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
			{	if(i*j>=num[k])
				{	g[i][j]=c[i*j][num[k]];
					for(int l=1;l<=i;++l)
					for(int r=1;r<=j;++r)
					if(l<i or r<j)
					g[i][j]=(g[i][j]-(((g[l][r]*c[i][l])%mod)*c[j][r])%mod+mod)%mod;
				}
			}
			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)
			{	int nx=i-l,ny=j-r;
				if(nx*ny>=num[k])
				f[i][j][k]=(f[i][j][k]+((f[l][r][k-1]*g[nx][ny]%mod*c[n-l][nx])%mod)%mod*c[m-r][ny]%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);
	}
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}
posted @ 2021-10-01 19:59  -zxb-  阅读(30)  评论(0编辑  收藏  举报