[AHOI2009] 中国象棋

类型:DP

传送门:>Here<

题意:给出一个$N*M$的棋盘,每行每列的棋子数不得超过$2$。求方案数

解题思路

这题的关键在于如何定义$dp$数组

考虑一行一行做,我们会发现对于第$i$行的方案数不取决于前面棋子的摆放顺序或特定位置,只取决于前面的每一列有多少棋子——只有每一列的棋子个数会影响当前这一行摆放的方案数。于是,令$dp[i][j][k]$表示前$i$行中,有$j$列含有$1$个棋子,$k$列含有$2$个棋子的方案数。我们可以形象地理解为:目前在放第$i$行的棋子,并且前面每一列的棋子数都已确定,现在我们要将棋子接在特定几列上

由于每行的棋子数不超过$2$,因此当前这一行最多放$2$个棋子。因此可以分类讨论:

  • 放0枚棋子

选择继承 $$dp[i][j][k]+=dp[i-1][j][k]$$

  • 放1枚棋子,在之前没有任何棋子的一列上

放了之后将会增加有一枚棋子的一列,并且共有$M-k-(j-1)$中空列的选择 $$dp[i][j][k]+=dp[i-1][j-1][k] * (M-k-(j-1))$$

  • 放1枚棋子,在之前有一枚棋子的一列上

增加一列$2$枚的,减少一列$1$枚的,有$j+1$种选择 $$dp[i][j][k]+=dp[i-1][j+1][k-1] * (j+1)$$

  • 放2枚棋子,都放在之前没有棋子的两列上

增加两列$1$枚的,有$C_{M-k-(j-2)}^{2}$种选择 $$dp[i][j][k]+=dp[i-1][j-2][k] * C_{M-k-(j-2)}^{2}$$

  • 放2枚棋子,都放在之前有一枚棋子的两列上

增加两列$2$枚的,减少两列$1$枚的,有$j+2$种选择 $$dp[i][j][k]+=dp[i-1][j+2][k-2] * (j+2)$$

  • 放2枚棋子,一枚放在之前没有棋子的那列,一枚放在之前有一枚棋子的那列

增加一列$1$枚的,增加一列$2$枚的,减少一列$1$枚的,总和起来增加一列$2$枚的,有$(M-(k-1)-j) * j / 2$种选择 $$dp[i][j][k]+=dp[i-1][j][k-1]*(M-(k-1)-j) * j / 2$$

注意判断边界情况……

本题的初始化是$dp[0][0][0]=1$。网上的文章都很少有对$DP$初始化的详细解释,我的理解是这样的:第一列在转移时不可能出现$k > 0$的情况,因为总共只有$1$行能放。换言之,第一行之前什么都没有。因此只能从$dp[0][0][0]$转移而来

我觉得关于$DP$初始化的问题还是很重要的,比如背包问题中是否恰好装满的问题。关于这方面的知识还需要慢慢积累!

Code

可能中间乘方案数的时候会乘爆,因此需要ll

/*By DennyQi 2018.8.17*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
#define int ll
const int MAXN = 10010;
const int MAXM = 27010;
const int INF = 1061109567;
const int MOD = 9999973;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();return x * w;
}
int N,M,ans;
int dp[105][105][105];
#undef int
int main(){
#define int ll
    N=r,M=r;
    dp[0][0][0] = 1;
    for(int i = 1; i <= N; ++i){
        for(int j = 0; j <= M; ++j){
            for(int k = 0; j + k <= M; ++k){
                dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k]) % MOD;
                if(j-1 >= 0){
                    dp[i][j][k] = (dp[i][j][k] + dp[i-1][j-1][k] * (M-k-j+1)) % MOD;
                }
                if(j+1 <= M && k-1 >= 0){
                    dp[i][j][k] = (dp[i][j][k] + dp[i-1][j+1][k-1] * (j+1)) % MOD;
                }
                if(j-2 >= 0){
                    dp[i][j][k] = (dp[i][j][k] + dp[i-1][j-2][k] * (M-k-j+2) * (M-k-j+1) / 2) % MOD;
                }
                if(j-1 >= 0 && k-1 >= 0){
                    dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k-1] * (M-k-j+1) * (j)) % MOD;
                }
                if(j+2 <= M && k-2 >= 0){
                    dp[i][j][k] = (dp[i][j][k] + dp[i-1][j+2][k-2] * (j+2) * (j+1) / 2) % MOD;
                }
            }
        }
    }
    for(int j = 0; j <= M; ++j){
        for(int k = 0; k <= M; ++k){
            if(j+k <= M){
                ans = (ans + dp[N][j][k]) % MOD;
            } 
        }
    }
    printf("%lld", ans);
}
原文地址:https://www.cnblogs.com/qixingzhi/p/9495064.html