放旗子[题解]
放旗子
这是一道计数 \(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\) 列的方案数。
理解公式
这个部分对上述公式进行解释,大家可以自己先思考一下为什么。
假设我们当前计算的是第 \(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\) 的求解公式即为:
注意一下这里的范围其实就是使其不和 \(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\) 列不必放满,但是棋子得放完,答案就是:
//不一定每行每列都要有棋子,但是棋子得放完
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;
}