[AHOI2009]中国象棋 dp+递推

原题:https://www.luogu.org/problemnew/show/P2051

题解:求在n*m中放炮的方案数,每行每列最多2个。

考虑50分的做法,将每一列的状态压缩,用3进制表示。即f(i,j)表示前i行,状态为j的方案数。讨论当前行放的棋子数。

<1> 当这一行不放时,f(i,j)=f(i-1,j)

<2> 放1个时:            f(i,j)=sum{ f(i-1,k) }k为可以转移的状态

<3> 放2个时:               f(i,j)=sum{f(i-1,k)} k为可以转移的状态

复杂度为O(n*3^m)

100分做法。之前的做法可以发现,不需要知道每个棋子的位置,如 1 0 0 0,表示在第1个位置放一个棋子,等效于1*4。也就是说,只要知道有多少个放1个的列,多少个只放2个的列,就可以通过数学算出来。

设:f[i][j][k] 为前i行,j列放1个棋子,k列放2个棋子。
考虑转移:
<1>不放  f[i][j][k]+= f[i-1][j][k]
<2>放1个 f[i][j][k]+= f[i-1][j+1][k-1]*(j+1) //放在有1个的列,填入后j--,k++,所以应从j+1,k-1枚举
         f[i][j][k]+= f[i-1][j-1][k]*(m-(j-1)-k)//放在空的行
<3>放2个 f[i][j][k]+= f[i-1][j+2][k-2]*C(j+2,2) //放在有1个的列,C(j+2,2)表示从j+2选2个
         f[i][j][k]+= f[i-1][j-2][k]*C(m-(j-2)-k,2)// 放在空行里
         f[i][j][k]+= f[i-1][j][k-1]*j*(m-(k-1)-j) //1个放在有1个列里,1个放在空行里

细节:

1.初值 f[0][0][0]=1

2.对于j+k>m的显然不合理,由于初值不会被转移到,所以无影响。

3.转移的条件很好像,只要让i,j,k>=0就行了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 
using namespace std;
const int N=110,M=9999973;
int  n,m;
ll f[N][N][N];
ll c(int n){
	return ((n*(n-1))/2)%M; 
} 
int  main(){
//	freopen("test.in","r",stdin);
	scanf("%d%d",&n,&m);	
	f[0][0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
			for(int k=0;k<=m;k++){
				f[i][j][k]+=f[i-1][j][k];f[i][j][k]%=M;
				// 放 1个 
				if(k-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k-1]*(j+1))%M; 
				if(j-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k]*(m-(j-1)-k))%M;
				 //放 2个
				if(k-2>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j+2][k-2]*c(j+2))%M;
				if(j-2>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j-2][k]*c(m-(j-2)-k))%M;
				if(k-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j][k-1]*j*(m-(k-1)-j))%M;
			}

	ll ans=0;
	for(int j=0;j<=m;j++){
		for(int k=0;k<=m;k++)
			ans=(ans+f[n][j][k])%M;	
	}
	printf("%lld\n",ans);
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/weixin_39689721/article/details/88384717
今日推荐