zoukankan      html  css  js  c++  java
  • [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);
    }
  • 相关阅读:
    Python 存储引擎 数据类型 主键
    Python 数据库
    Python 线程池进程池 异步回调 协程 IO模型
    Python GIL锁 死锁 递归锁 event事件 信号量
    Python 进程间通信 线程
    Python 计算机发展史 多道技术 进程 守护进程 孤儿和僵尸进程 互斥锁
    Python 异常及处理 文件上传事例 UDP socketserver模块
    Python socket 粘包问题 报头
    Django基础,Day7
    Django基础,Day6
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9495064.html
Copyright © 2011-2022 走看看