BZOJ1801: [Ahoi2009]chess 中国象棋
BZOJ1801: [Ahoi2009]chess 中国象棋
Description
在N行M列的棋盘上,放若干个炮可以是0个,使得没有任何一个炮可以攻击另一个炮。 请问有多少种放置方法,中国像棋中炮的行走方式大家应该很清楚吧.
Input
一行包含两个整数N,M,中间用空格分开.
Output
输出所有的方案数,由于值比较大,输出其mod 9999973
Sample Input
1 3
Sample Output
7
HINT
除了在3个格子中都放满炮的的情况外,其它的都可以.
100%的数据中N,M不超过100
50%的数据中,N,M至少有一个数不超过8
30%的数据中,N,M均不超过6
题解Here!
一眼看去——棋盘$DP$。
首先,每行、每列最多放俩炮,这个不用多说。
对于$50%$的数据,直接状压$DP$对吧。
然后我们发现,我们对于棋子的顺序与位置并不关心。
也就是说,并不需要知道准确的状态。
那么我们就可以把状态压缩省去了,换成讲的状态记录。
设$dp[i][j][k]$表示当前正在填第$i+1$行,前$i$行中有$j$列放了一个棋子,有$k$列放了两个棋子。
然后转移就是分类讨论,运用加法原理和乘法原理即可,具体可以参见代码。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 110 #define MOD 9999973 using namespace std; int n,m; long long ans=0,dp[MAXN][MAXN][MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline int C(int x){return x*(x-1)/2;} void solve(){ dp[0][0][0]=1; for(int i=0;i<n;i++) for(int j=0;j<=m;j++) for(int k=0;j+k<=m;k++) if(dp[i][j][k]){ dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%MOD;//一个棋子都不放 if(m-k-j>=1)//放一个,在没有棋子的那一列 dp[i+1][j+1][k]=(dp[i+1][j+1][k]+dp[i][j][k]*(m-j-k)%MOD)%MOD; if(j>=1)//放一个,在有一个棋子的那一列 dp[i+1][j-1][k+1]=(dp[i+1][j-1][k+1]+dp[i][j][k]*j%MOD)%MOD; if(m-k-j>=1&&j>=1)//放两个,一个在没有棋子的列,一个在有一个棋子的列 dp[i+1][j][k+1]=(dp[i+1][j][k+1]+dp[i][j][k]*(m-j-k)%MOD*j%MOD)%MOD; if(m-k-j>=2)//放两个,都在没有棋子的两列 dp[i+1][j+2][k]=(dp[i+1][j+2][k]+dp[i][j][k]*C(m-j-k)%MOD)%MOD; if(j>=2)//两个,在一个棋子的列 dp[i+1][j-2][k+2]=(dp[i+1][j-2][k+2]+dp[i][j][k]*C(j)%MOD)%MOD; } } void work(){ n=read();m=read(); solve(); for(int i=0;i<=m;i++) for(int j=0;i+j<=m;j++) ans=(ans+dp[n][i][j])%MOD;//统计方案数 printf("%lld\n",ans); } int main(){ work(); return 0; }