[AHOI2009]中国象棋(luogu P2051)
原题链接:https://www.luogu.org/problem/show?pid=2051
本题是一道DP,思维难度主要是在多种状态的考虑上。
对于每一行,最多放两个炮,所以,对于每一行,只会有放一个炮,放两个炮或者不放炮的三种情况
以f[i][j][k]表示前i行中,有j列有1个炮,k列有两个炮,则有m-j-k列没有炮,a[i]记录C(i,2)。
1.不放炮:直接将本行的方案数加到下一行即可。
2.放一个炮,放在一个空列上。要注意空行有m-j-k个,所以要乘上m-j-k再加到下一行
3.放一个炮,放在一个有一个炮的列上。
尤其要注意,此时可能没有任何一列只有一个炮的,所以要特判能否进行此次转移。
4.放两个炮,都放在空列上。空列有(m-j-k)个,而从中选两个放炮,就有C(m-i-j,2)种选法。
5.放两个炮,分别放在一个空列上和一个只有一个炮的列上,其实相当于在一个空列上放了两个炮,空列有(m-j-k)个,只有一个炮的列有j个。
6.放两个炮,都放在只有一个炮的列上,首先特判j>=2时才进行转移,有C(j,2)种选法。
由此,我们就能得到六个转移方程(未特判):
f[i+1][j][k]+=f[i][j][k]; f[i+1][j+1][k]+=((f[i][j][k])*(m-k-j))%c; f[i+1][j-1][k+1]+=(f[i][j][k]*j)%c; f[i+1][j+2][k]+=(f[i][j][k]*a[m-j-k])%c; f[i+1][j][k+1]+=(f[i][j][k]*(m-j-k)*j)%c; f[i+1][j-2][k+2]+=(f[i][j][k]*a[j])%c;
转移完成后统计答案,f[i][j][k]中j+k<=m的方案符合要求,加入答案中(j+k>m的情况显然是不合实际的)
#include<cstdio> const int c=9999973; long long ans,f[105][105][105];//前i行j 1,k 2 m-i-j 0 int n,m,a[105]; int main() { scanf("%d %d",&n,&m); for(int i=2;i<=100;i++) a[i]=(i*(i-1))/2; f[0][0][0]=1; for(int i=0;i<n;i++) { for(int j=0;j<=m;j++) { for(int k=0;k<=m;k++) { f[i][j][k]%=c; f[i+1][j][k]+=f[i][j][k]; f[i+1][j+1][k]+=((f[i][j][k])*(m-k-j))%c; if(j>0) f[i+1][j-1][k+1]+=(f[i][j][k]*j)%c; f[i+1][j+2][k]+=(f[i][j][k]*a[m-j-k])%c; f[i+1][j][k+1]+=(f[i][j][k]*(m-j-k)*j)%c; if(j>1) f[i+1][j-2][k+2]+=(f[i][j][k]*a[j])%c; } } } for(int i=0;i<=m;i++) { for(int j=0;i+j<=m;j++) ans=(ans+f[n][i][j])%c; } printf("%lld",ans); return 0; }