当前位置: 代码迷 >> 综合 >> 洛谷P2051 中国象棋 状压dp
  详细解决方案

洛谷P2051 中国象棋 状压dp

热度:26   发布时间:2024-02-10 04:40:33.0

P2051 [AHOI2009]中国象棋 状压dp

思路

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

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

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

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

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

  • \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]}。
  • \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)}
    \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)}
  • \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}
  • \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)}
  • \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}
  • \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_LOCALfreopen("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));
#elsesolve();
#endifreturn 0;
}