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();}