BZOJ 1801: [Ahoi2009]chess 中国象棋

Description

N 100 M 100 列的棋盘上,放若干个炮可以是 0 个,使得没有任何一个炮可以攻击另一个炮,请问有多少种放置方法。
一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。

Solution

每行每列最多放两个炮。

不难想到用 f [ i ] [ j ] [ k ] [ a ] [ b ] [ c ] [ d ] 表示前 i 行放了 j 个炮,其中第 i 行放了 k 个,分别在 a 列和 b 列, a 列放了 c 个炮, b 列放了 d 个炮的方案数。其中 0 k , c , d 2

这样的DP时间和空间复杂度都是 O ( n 4 ) 的,可以过 50 % 的数据,考虑怎样优化。

考虑怎样把 a , b 合成一个。不妨先做每行每列只放一个的,再把它用乘法原理合并成每行每列放两个的。

那么用 f [ i ] [ j ] [ k ] [ l ] 表示前 i 行放了 j 个炮,其中第 i 行的炮放在了第 j 列,第 j 列有 l 个炮的方案数( j = 0 表示不放)。

但最后的答案并不能直接平方,因为会有重复的情况,比如:

这怎么办呢?


劳资弃了上面的想法QAQ

f [ i ] [ j ] [ k ] 表示前 i 行有 j 列是一个棋子, k 列有两个棋子的方案数。

那么

(25) f [ i ] [ j ] [ k ] = f [ i 1 ] [ j ] [ k ] (26) + f [ i 1 ] [ j 1 ] [ k ] × ( m j k + 1 1 ) (27) + f [ i 1 ] [ j + 1 ] [ k 1 ] × ( j + 1 1 ) (28) + f [ i 1 ] [ j 2 ] [ k ] × ( m j k + 2 2 ) (29) + f [ i 1 ] [ j + 2 ] [ k 2 ] × ( j + 2 2 ) (30) + f [ i 1 ] [ j ] [ k 1 ] × ( m j k + 1 1 ) ( j 1 )

这题跟 好像啊。

Error
  • 一上来就想错,没有仔细验证……

  • f [ i 1 ] [ j ] [ k 1 ] × ( m j k + 1 1 ) ( j 1 ) 这个式子,要保证 j > 0 才行,因为要把一个有一个棋子的列变成有两个棋子的。

  • 初始化直接让 f [ 0 ] [ 0 ] [ 0 ] = 1 就可以。

Code
#include <cstdio>

const int N = 105, P = 9999973;
int f[N][N][N];

int main() {
    int n, m; scanf("%d%d", &n, &m);
    f[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) {
                f[i][j][k] = f[i-1][j][k];
                if (j > 0) f[i][j][k] = (f[i][j][k] + 1LL * f[i-1][j-1][k] * (m - j - k + 1)) % P;
                if (j > 1) f[i][j][k] = (f[i][j][k] + 1LL * f[i-1][j-2][k] * (m - j - k + 2) * (m - j - k + 1) / 2) % P;
                if (j < m && k > 0) (f[i][j][k] = f[i][j][k] + 1LL * f[i-1][j+1][k-1] * (j + 1)) % P;
                if (j > 0 && k > 0) f[i][j][k] = (f[i][j][k] + 1LL * f[i-1][j][k-1] * (m - j - k + 1) * j) % P;
                if (j < m - 1 && k > 1) f[i][j][k] = (f[i][j][k] + 1LL * f[i-1][j+2][k-2] * (j + 2) * (j + 1) / 2) % P;
            }
        }
    }

    int ans = 0;
    for (int j = 0; j <= m; ++j)
        for (int k = 0; k <= m - j; ++k)
            ans = (ans + f[n][j][k]) % P;
    printf("%d\n", ans);

    return 0;
}

QAQ另附上每行每列只有一个棋子的代码:

#include <cstdio>

const int N = 105, P = 9999973;
int f[N][N][N][2];

int main() {
    int n, m; scanf("%d%d", &n, &m);

    for (int i = 0; i <= m; ++i) f[0][0][i][0] = f[0][0][i][1] = 1;

    for (int i = 1; i <= n; ++i) { //前i行 
        for (int j = 0; j <= i; ++j) { //放了j个棋子 
            int sum = 0;
            if (j) for (int k = 1; k <= m; ++k) { //第i行放一个棋子在第k列 
                f[i][j][k][1] = (f[i][j][k][1] + f[i-1][j-1][k][0]) % P;
                sum = (sum + f[i][j][k][1]) % P;
            }
            for (int k = 0; k <= m; ++k) { //第i行不放棋子 
                if (j == 0 && k) break;
                f[i][j][0][1] = (f[i][j][0][1] + f[i-1][j][k][1]) % P;
                sum = (sum + f[i][j][0][1]) % P;
            }
            for (int k = 1; k <= m; ++k) { //第k列没有棋子 
                f[i][j][k][0] = (sum - f[i][j][k][1] + P) % P;
            }
        }
    }

    int ans = 0;
    for (int i = 0; i <= n; ++i)
        for (int j = 0; j <= m; ++j)
            ans = (ans + f[n][i][j][1]) % P;
    printf("%d\n", ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Milkyyyyy/article/details/82025133