• [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);
    }
  • 相关阅读:
    java(样品集成框架spring、spring mvc、spring data jpa、hibernate)
    设定十分钟android在状态栏上集成的开源project推荐
    分析javascript关闭
    排列-条件求和(Code)
    Leetcode: Remove Duplicates from Sorted Array
    怎样将baidu地图中的baidu logo 去掉
    Android自适应不同屏幕几种方法
    浏览器兼容性问题解决方式
    XMPP入门
    “聊天剽窃手”--ptrace进程注入型病毒
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9495064.html
Copyright © 2020-2023  润新知