洛谷P2051 中国象棋 状压dp

P2051 [AHOI2009]中国象棋 状压dp

思路

因 为 在 同 一 行 或 同 一 列 中 最 多 放 两 个 炮 , 所 以 可 以 放 0 、 1 、 2 个 炮 , 三 个 以 上 的 就 不 合 法 , 因 为 可 以 互 相 打 。 因为在同一行或同一列中最多放两个炮,所以可以放0、1、2个炮,三个以上的就不合法,因为可以互相打。 012

对 于 状 态 压 缩 d p , 首 先 需 要 假 设 状 态 , 这 题 的 状 态 就 是 0 、 1 、 2 , ( 列 是 指 第 i 行 中 1   m 列 ) 0 代 表 一 列 中 没 有 炮 , 1 代 表 一 列 中 有 一 个 炮 , 2 代 表 一 列 中 有 2 个 炮 , 从 第 一 行 往 下 遍 历 , 寻 找 所 有 的 状 态 。 对于状态压缩dp,首先需要假设状态,这题的状态就是0、1、2,(列是指第i行中1~m列)0代表一列中没有炮,1代表一列中有一个炮,2代表一列中有2个炮,从第一行往下遍历,寻找所有的状态。 dp012i1 m0122

所 以 我 们 定 义 一 个 数 组 d p [ i ] [ j ] [ k ] , 第 一 维 表 示 前 i 行 , 第 二 维 表 示 有 j 列 只 有 一 个 炮 , 第 三 维 表 示 只 有 k 列 有 两 个 炮 \red{所以我们定义一个数组dp[i][j][k],第一维表示前i行,第二维表示有j列只有一个炮,第三维表示只有k列有两个炮} dp[i][j][k]ijk

那 么 根 据 容 斥 原 理 可 知 , 没 有 炮 的 列 数 有 m − j − k 列 。 \red{那么根据容斥原理可知,没有炮的列数有m-j-k列。} mjk

因 为 一 列 中 最 多 放 两 个 棋 子 , 且 对 于 每 一 行 , 每 一 列 只 能 放 一 个 , 不 能 说 放 两 个 , 难 道 让 他 们 结 合 吗 ? 这 里 笔 者 就 想 当 然 的 结 合 了 , 然 后 就 错 了 。 因为一列中最多放两个棋子,且对于每一行,每一列只能放一个,不能说放两个,难道让他们结合吗?这里笔者就想当然的结合了,然后就错了。
所 以 我 们 可 以 讨 论 以 下 三 种 情 况 ( 所 有 情 况 的 第 i 行 都 是 由 第 i − 1 行 继 承 下 来 ) : 所以我们可以讨论以下三种情况(所有情况的第i行都是由第i-1行继承下来): ii1

  • 不 放 棋 子 \blue{不放棋子}
    第 i 行 的 状 态 可 以 由 第 i − 1 行 状 态 继 承 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] 。 第i行的状态可以由第i-1行状态继承,即\red{dp[i][j][k]=dp[i - 1][j][k]}。 ii1dp[i][j][k]=dp[i1][j][k]
  • 放 一 个 棋 子 \blue{放一个棋子}
    该 棋 子 放 在 有 一 个 棋 子 的 列 上 \blue{该棋子放在有一个棋子的列上}
    在 j 列 中 拿 出 一 列 + 1 个 棋 子 成 为 k 列 中 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k + 1 ] ∗ ( j + 1 ) 在j列中拿出一列+1个棋子成为k列中的一员,即\red{dp[i][j][k]=dp[i-1][j][k+1]*(j+1)} j+1kdp[i][j][k]=dp[i1][j][k+1](j+1)
    该 棋 子 放 在 空 列 上 \blue{该棋子放在空列上}
    在 空 列 中 拿 出 一 列 成 为 j 列 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 1 ] [ k ] ∗ ( m − j − k + 1 ) 在空列中拿出一列成为j列的一员,即\red{dp[i][j][k]=dp[i-1][j-1][k]*(m-j-k+1)} jdp[i][j][k]=dp[i1][j1][k](mjk+1)
  • 放 两 个 棋 子 \blue{放两个棋子}
    两 个 棋 子 分 别 放 在 两 个 空 列 上 \blue{两个棋子分别放在两个空列上}
    在 空 列 中 拿 出 两 列 成 为 j 列 的 两 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 2 ] [ k ] ∗ C m − j − k + 2 2 在空列中拿出两列成为j列的两员,即\red{dp[i][j][k]=dp[i-1][j-2][k]*C_{m-j-k+2}^2} jdp[i][j][k]=dp[i1][j2][k]Cmjk+22
  • 两 个 棋 子 一 个 在 有 棋 子 的 列 一 个 在 空 列 中 \blue{两个棋子一个在有棋子的列一个在空列中}
    在 j 列 中 拿 出 一 个 成 为 k 列 的 一 员 , 在 空 列 中 拿 出 一 个 成 为 j 列 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k − 1 ] ∗ j ∗ ( m − j − k + 1 ) 在j列中拿出一个成为k列的一员,在空列中拿出一个成为j列的一员,即\red{dp[i][j][k]=dp[i-1][j][k-1]*j*(m-j-k+1)} jkjdp[i][j][k]=dp[i1][j][k1]j(mjk+1)
  • 两 个 棋 子 分 别 放 在 两 个 有 棋 子 的 列 上 \blue{两个棋子分别放在两个有棋子的列上}
    在 j 列 拿 出 两 个 成 为 k 列 的 两 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j + 2 ] [ k − 2 ] ∗ C j + 2 2 在j列拿出两个成为k列的两员,即\red{dp[i][j][k]=dp[i-1][j+2][k-2]*C_{j+2}^2} jkdp[i][j][k]=dp[i1][j+2][k2]Cj+22
  • 两 个 棋 子 放 在 没 有 棋 子 的 一 列 上 \blue{两个棋子放在没有棋子的一列上}
    这 是 错 误 的 , 上 面 说 道 , 对 于 每 一 行 , 只 能 在 一 列 中 放 一 个 , 不 能 放 两 个 , 否 则 就 会 “ 结 合 ” 了 。 \red{这是错误的,上面说道,对于每一行,只能在一列中放一个,不能放两个,否则就会“结合”了。}

Code

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;

#define INF 0x7f7f7f
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)

// const ll mod = 998244353;
// const int maxn = 1e5 + 10;
// const double eps = 1e-6;

const int mod =  9999973;

ll dp[105][105][105];

int n, m;

void solve() {
    
    
    cin >> n >> m;
    dp[0][0][0] = 1;
    for(int i = 1;i <= n; i++) {
    
    
        for(int j = 0;j <= m; j++) {
    
    
            for(int k = 0;k <= m - j; k++) {
    
    
                dp[i][j][k] = dp[i - 1][j][k]; // 不放棋子
                if(k >= 1) dp[i][j][k] += dp[i - 1][j + 1][k - 1] * (j + 1); // 放一个棋子在有一个棋子的列
                if(j >= 1) dp[i][j][k] += dp[i - 1][j - 1][k] * (m - j + 1 - k); // 放一个棋子在没有棋子的列
                if(j >= 2) dp[i][j][k] += dp[i - 1][j - 2][k] * ((m - j - k + 2) * (m - j - k + 1) / 2); // 放两个棋子在没有棋子的两列
                if(k >= 1) dp[i][j][k] += dp[i - 1][j][k - 1] * j * (m - j - k + 1); // 放两个在一列有棋子一列无棋子
                if(k >= 2) dp[i][j][k] += dp[i - 1][j + 2][k - 2] * ((j + 2) * (j + 1) / 2); // 放两个在两列都有棋子
                dp[i][j][k] %= mod;
            }
        }
    }
    ll ans = 0;
    for(int j = 0;j <= m; j++) {
    
    
        for(int k = 0;k <= m - j; k++) {
    
    
            ans = (ans + dp[n][j][k]) % mod;
        }
    }
    cout << ans << endl;
}

signed main() {
    
    
    ios_base::sync_with_stdio(false);
    //cin.tie(nullptr);
    //cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed test_index_for_debug = 1;
    char acm_local_for_debug = 0;
    do {
    
    
        if (acm_local_for_debug == '$') exit(0);
        if (test_index_for_debug > 20)
            throw runtime_error("Check the stdin!!!");
        auto start_clock_for_debug = clock();
        solve();
        auto end_clock_for_debug = clock();
        cout << "Test " << test_index_for_debug << " successful" << endl;
        cerr << "Test " << test_index_for_debug++ << " Run Time: "
             << double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
    solve();
#endif
    return 0;
}

猜你喜欢

转载自blog.csdn.net/fztsilly/article/details/107963543