1. 程式人生 > >【[AHOI2009]中國象棋】

【[AHOI2009]中國象棋】

計數類dp還是要多寫啊

看上去並沒有什麼思路,加上被題解裡狀壓的標籤迷惑了,於是就去看了一眼題解裡設計的狀態

之後就很好做了

首先先搞明白這道題的本質,就是對於任何一行任何一列炮的個數都不能超過\(2\)

我們設\(dp[i][j][k]\)表示到了第\(i\)一共有\(j\)列的炮個數為\(2\),有\(k\)列個數為\(1\)的總方案數

那麼一個炮都沒有放的列數自然是\(m-k-j\)

之後就可以隨便做了

對於每一行我們有三種選擇

  1. 不放

  2. 放一個

  3. 放兩個

之後這就是我們的核心思想了

一共有五種轉移,就是一些簡單的計數原理和組合數學啦

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 105
#define LL long long
const LL P=9999973;
const LL inv=4999987;//2在P意義下的逆元
int n,m;
LL dp[maxn][maxn][maxn];
int main()
{
    scanf("%d%d",&n,&m);
    dp[1][0][0]=1;
    dp[1][0][1]=m;
    dp[1][0][2]=m*(m-1)*inv%P;
    for(re int i=1;i<n;i++)
        for(re int j=0;j<=m;j++)
            for(re int k=0;k<=m;k++)
            {
                if(j+k>m) continue;
                int p=m-k-j;
                if(!dp[i][j][k]) continue;
                dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%P;//這一行什麼都不放 
                if(j+1<=m&&k-1>=0) 
                    dp[i+1][j+1][k-1]=(dp[i+1][j+1][k-1]+dp[i][j][k]*k%P)%P;//在原來有1個炮的列上放 
                if(p&&k+1<=m&&j+k+1<=m) 
                    dp[i+1][j][k+1]=(dp[i+1][j][k+1]+dp[i][j][k]*p%P)%P;//在原來有0個炮的列上放 
                if(j+2<=m&&k-2>=0) 
                    dp[i+1][j+2][k-2]=(dp[i+1][j+2][k-2]+(dp[i][j][k]*(k-1)*k%P)*inv)%P;//在兩個原來有1的上放 
                if(p>=2&&k+2<=m&&j+k+2<=m) 
                    dp[i+1][j][k+2]=(dp[i+1][j][k+2]+(dp[i][j][k]*(p-1)*p)%P*inv)%P;//在兩個原來有0的上放 
                if(p&&j+1<=m&&k&&j+1+k<=m)
                    dp[i+1][j+1][k]=(dp[i+1][j+1][k]+dp[i][j][k]*k*p%P)%P;//在一個原來是0,一個原來是1上放 
                
            }
    LL ans=0;
    for(re int i=0;i<=m;i++)
    for(re int j=0;j<=m;j++)
    if(i+j<=m) ans=(ans+dp[n][i][j])%P;
    std::cout<<ans;
    return 0;
}