洛谷P5005

洛谷传送门

吐槽

并不算个难题,主要是调的时间稍微有点久了,所以放上来记录一下 水一下博客

思路

数据范围很小,又是求方案数类的问题,不关心具体方案,不关心具体方案会让我们想到 \({\rm DP}\) 的做法。数据很小又让我们想到了状态压缩的进行 \({\rm DP}\) 。另外这道题属实和 炮兵阵地 很是相似,故而可以猜测,这也许是一个状压 \({\rm DP}\) 吧,不妨设 \(f[i][S]\) 表示前 \(i\) 行,第 \(i\) 行状态为 \(S\) 的合法方案数,发现此时并不能转移,因为假设暴力枚举了上一行,我们并不一定能把上一行所有状态都拿过来加,理由是我们不知道上上行具体如何,所以发现此时应该记录 \({\rm f[i][now][lst]}\) ,暨连续记录这一层和上一层比较合理,对于第一层,由于没有上一层,故预处理,对于第二层,由于只有一层限制,故也预处理,然后就是后面的转移了,我们暴力枚举这一层,上一层,上上层,分别判断是否有冲突即可。

至于空间可能会开不下的问题,使用滚动数组就行了,应该可以压成 \(2\) 毕竟每一个状态压两行,所以转移只需要上一行,不过代码中实现是 \(3\)

预处理:

预处理:( \({\sf ps:}\) 其中的 \({\rm pls}\) 是取模优化的加法,理解为加号即可,至于 \({\rm jump1}\) 很快就会讲到,是用来判断冲突的)。

    for (int i = 0; i < (1 << m); ++i) dp[1][i][0] = 1; //! 第一行随便选

    for (int j = 0; j < (1 << m); ++j) {
        for (int k = 0; k < (1 << m); ++k) { //! 枚举第一行和第二行
            if ((jump1(j) & k) || (jump1(k) & j)) {}
            else {
                // dp[2][k][j] = 1;
                pls(dp[2][k][j], dp[1][j][0]);
                // print(k), print(j);
                // system("pause");
            }
        }
    }

冲突判断 (一些简化结构的函数):

具体来说,我们可以对于一层,暴力枚举每一匹马,然后看左右是不是蹩马腿,如果不,就记录可以攻击的位置,把可以攻击的位置压成一个二进制数传回去。
实现:

int jump1(int state) {
    int re = 0;
    for (int j = 0; j < m; ++j) {
        if ((state >> j) & 1) {
            if (j > 1) {
                if ((state >> j - 1) & 1) {}
                else {
                    re |= (1 << j - 2);
                }
            }
            if (j < m - 2) {
                if ((state >> j + 1) & 1) {}
                else {
                    re |= (1 << j + 2);
                }
            }
        }
    }
    return re;
}

对于马隔两行攻击,我们只需要把两行状态都传进去,就可以知道应该攻击到哪些点了。
实现:

int jump2(int state1, int state2) {
    int re = 0;
    for (int j = 0; j < m; ++j) {
        if ((state1 >> j) & 1) {
            if (j > 0) {
                if ((state2 >> j) & 1) {}
                else {
                    re |= (1 << j - 1);
                }
            }
            if (j < m - 1) {
                if ((state2 >> j) & 1) {}
                else {
                    re |= (1 << j + 1);
                }
            }
        }
    }
    return re;
}

转移主体:

那么目前,我们已经有了预处理,以及如何判断冲突,接下来就是比较容易的转移了。

for (int i = 3; i <= n; ++i) {
        // std::memset(dp[i % 3], 0, sizeof dp[i % 3]);
        for (int j = 0; j < (1 << m); ++j) {
            for (int k = 0; k < (1 << m); ++k) {
                dp[i % 3][j][k] = 0;
                if (jump1(k) & j) continue;
                if (jump1(j) & k) continue;
                for (int w = 0; w < (1 << m); ++w) {
                    if (jump1(w) & k) continue;
                    if (jump1(k) & w) continue;
                    if (jump2(w, k) & j) continue;
                    if (jump2(j, k) & w) continue;
                    // print(w), print(j), print(k);
                    pls(dp[i % 3][j][k], dp[(i - 1) % 3][k][w]);
                }
                // if (i == n) pls(re, dp[i % 3][j][k]);
            }
        }
    }

小疑问

这里的统计答案被注释掉了,不知道为什么会 \({\rm WA}\) 一个点,所以换了一种统计答案的方式,如果有知道的巨佬可以 \({\rm educate}\) 我一下。

整体 \(\large{\rm Code}\)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>

const int md = 1e9 + 7;

int n, m;

int dp[3][1 << 6][1 << 6];

int jump1(int state) {
    int re = 0;
    for (int j = 0; j < m; ++j) {
        if ((state >> j) & 1) {
            if (j > 1) {
                if ((state >> j - 1) & 1) {}
                else {
                    re |= (1 << j - 2);
                }
            }
            if (j < m - 2) {
                if ((state >> j + 1) & 1) {}
                else {
                    re |= (1 << j + 2);
                }
            }
        }
    }
    return re;
}
int jump2(int state1, int state2) {
    int re = 0;
    for (int j = 0; j < m; ++j) {
        if ((state1 >> j) & 1) {
            if (j > 0) {
                if ((state2 >> j) & 1) {}
                else {
                    re |= (1 << j - 1);
                }
            }
            if (j < m - 1) {
                if ((state2 >> j) & 1) {}
                else {
                    re |= (1 << j + 1);
                }
            }
        }
    }
    return re;
}
inline void pls(int &a, int b) { (a += b) >= md && (a -= md); }

inline void print(int x) {
    int i = 0;
    while (i < m) putchar(x % 2 + '0'), x /= 2, ++i;
    puts("");
}

signed main() {
    scanf("%d%d", &n, &m);
    if (n == 1) {
        return printf("%d\n", (1 << m)), 0;
    }

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

    for (int j = 0; j < (1 << m); ++j) {
        for (int k = 0; k < (1 << m); ++k) { //! 枚举第一行和第二行
            if ((jump1(j) & k) || (jump1(k) & j)) {}
            else {
                // dp[2][k][j] = 1;
                pls(dp[2][k][j], dp[1][j][0]);
                // print(k), print(j);
                // system("pause");
            }
        }
    }

    int re = 0;
    for (int i = 3; i <= n; ++i) {
        // std::memset(dp[i % 3], 0, sizeof dp[i % 3]);
        for (int j = 0; j < (1 << m); ++j) {
            for (int k = 0; k < (1 << m); ++k) {
                dp[i % 3][j][k] = 0;
                if (jump1(k) & j) continue;
                if (jump1(j) & k) continue;
                for (int w = 0; w < (1 << m); ++w) {
                    if (jump1(w) & k) continue;
                    if (jump1(k) & w) continue;
                    if (jump2(w, k) & j) continue;
                    if (jump2(j, k) & w) continue;
                    // print(w), print(j), print(k);
                    pls(dp[i % 3][j][k], dp[(i - 1) % 3][k][w]);
                }
                // if (i == n) pls(re, dp[i % 3][j][k]);
            }
        }
    }
    for (int i = 0; i < (1 << m); ++i) {
        for (int j = 0; j < (1 << m); ++j) {
            if (jump1(i) & j) continue;
            if (jump1(j) & i) continue;
            pls(re, dp[n % 3][i][j]);
        }
    }
    printf("%d\n", re);
}

一些小细节:

这里总结我犯得一些错误,也许会有所帮助 虽然显然大部分人不会和我一样蠢

  1. 错误的把象棋马的攻击当成可逆的了,也就是以为 \(A\) 能攻击 \(B\) 必然意味着 \(B\) 可以攻击 \(A\) ,后来发现不是,有可能有一个被蹩马腿,所以应该两个都判断一下。
  2. 滚动数组用的时候需要清空,否则会复用,导致结果算错。
  3. 位运算的时候就用位运算,跳跃函数用了加法,导致多个点可以攻击同个位置的时候,进位了,出现错误。
posted @ 2021-04-08 21:58  Z_char  阅读(124)  评论(0编辑  收藏  举报