題解:bzoj1801: [Ahoi2009]chess 中國象棋
Description
在N行M列的棋盤上,放若幹個炮可以是0個,使得沒有任何一個炮可以攻擊另一個炮。 請問有多少種放置方法,中國像棋中炮的行走方式大家應該很清楚吧.Input
一行包含兩個整數N,M,中間用空格分開.Output
輸出所有的方案數,由於值比較大,輸出其mod 9999973Sample Input
1 3Sample Output
7HINT
除了在3個格子中都放滿炮的的情況外,其它的都可以.
100%的數據中N,M不超過100
50%的數據中,N,M至少有一個數不超過8
30%的數據中,N,M均不超過6
題解:
這道題狀態轉移方程真的好煩啊.....
考慮從上到下一行一行的放,那麽我們在放第i行時,顯然,每一行或每一列,最多放兩個炮。所以,在放第i行時,所有的合法放法其實是確定的了
放法1:一個都不放
放法2:僅放一個,放在一個炮的沒有列上
放法3:僅放一個,放在有一個炮的列上
放法4:放兩個,均放在一個炮都沒有的列上
放法5:放兩個,均放在有一個炮的列上
放法6:放兩個,一個放在有一個炮的列上,另一個放在有一個炮的列上
實際上到這裏我們可以發現:在放第i列時,我們並不關心炮的具體位置,因為我們求的是每種放法的方案數,所以,我們需要的信息只是每一種類型的列數的數量,這其實是一種對信息的壓縮(一個炮的有多少列,兩個炮的有多少列,一個炮都沒有的有多少列)
所以,用f[i][j][k]表示放到第i行時,有一個炮的有j列,有兩個炮的有k列,則一個炮都沒有的就有m-j-k列
然後考慮每種放法的合法方案數,用計數原理和組合數計算即可
狀態轉移方程如下:
f[i][j][k]=f[i-1][j][k]%MOD;
if(j-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j-1][k]*(m-j-k+1)%MOD)%MOD;
if(k-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j+1][k-1]*(j+1)%MOD)%MOD;
if(j-2>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j-2][k]*C(m-k-j+2)%MOD)%MOD;
if(k-2>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j+2][k-2]*C(j+2)%MOD)%MOD;
if(k-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j][k-1]*j*(m-j-k+1)%MOD)%MOD;
#include<bits/stdc++.h> #define MAXN 101 #define MOD 9999973 using namespace std; int n,m; long long ans=0; long long f[MAXN][MAXN][MAXN]; long long C(int N) { return ((N-1)*N)/2; } void DP() { for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) for(int k=0;k<=m-j;k++) { f[i][j][k]=f[i-1][j][k]%MOD; if(j-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j-1][k]*(m-j-k+1)%MOD)%MOD; if(k-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j+1][k-1]*(j+1)%MOD)%MOD; if(j-2>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j-2][k]*C(m-k-j+2)%MOD)%MOD; if(k-2>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j+2][k-2]*C(j+2)%MOD)%MOD; if(k-1>=0) f[i][j][k]=(f[i][j][k]%MOD+f[i-1][j][k-1]*j*(m-j-k+1)%MOD)%MOD; } } int main() { memset(f,0,sizeof(f)); scanf("%d%d",&n,&m); f[0][0][0]=1; DP(); for(int i=0;i<=m;i++) for(int j=0;j<=m-i;j++) ans=(ans%MOD+f[n][i][j]%MOD)%MOD; printf("%lld",ans); return 0; }View Code
題解:bzoj1801: [Ahoi2009]chess 中國象棋