31 NOI2001 炮兵阵地 题解

炮兵阵地

题面

将军们打算在 N×M 的网格地图上部署他们的炮兵部队。

一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地( H ),也可能是平原( P ),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

1185_1.jpg

在满足炮兵部队不能相互攻击的条件下,最多能够摆放多少炮兵部队。

求出最多能摆放的炮兵部队数量

\(1 \le N \le 100, 1 \le M \le 10\)

题解

解法1

这道题也是状态压缩dp,还是以行为阶段转移

考虑要摆第 \(i\) 行需要知道那些行的信息,显然,我们需要知道第 \(i - 2,i - 1\) 行的信息

所以设 \(f(i,j,k)\) 表示摆到第 \(i\) 行,第 \(i,i - 1\) 行的状态分别为 \(j,k\) 最多能摆多少炮兵

直接做的时间复杂度为 \(O(N2^{3M})\) 很显然不行,所以我们要将合法状态预处理出来

同一行内不冲突的状态最多也就 60 个,所以时间复杂度为 \(O(N60^3)\) ,空间可以用滚动数组或者vector优化

解法2

我们这里提供一种“搜索状压”的做法,将每个点的属性进行分类

我们分成如下几类

  • 0表示当前行以及上面一行的这个位置没有放炮兵
  • 1表示当前行的这个位置没有放炮兵,但是上一行放了
  • 2表示当前行的这个位置放了炮兵

那我们在放一个炮兵的时候一定是形如这样的(先只考虑前面的行,因为我们从前往后填)

\[\begin{align} &1 \\ &0 \\ \le 1 \quad \le 1 \quad &0 \quad \le 1 \quad \le 1 \\ \end{align} \]

我们可以将中间 0 的位置改为 2

从而变成这样

\[\begin{align} &1 \\ &0 \\ \le 1 \quad \le 1 \quad &2 \quad \le 1 \quad \le 1 \\ &1 \\ &0 \end{align} \]

我们转移的时候发现好像不太好转移,所以我们利用 \(dfs\) 来帮助我们转移

具体来讲,就是对于每一行,根据枚举的上一行的状态推出一个基本状态,然后再基本状态的基础上

枚举列,然后尝试在这个位置放一个炮兵,修改状态,递归到下个状态

然后回溯

我们用三进制储存每一行的状态,这样就不怕他卡空间了,空间复杂度为 \(O(n3^m)\)

时间复杂度上界为 \(O(nm3^{2m})\) ,不过由于 \(dfs\) 的剪枝,实际到不了那么高

code

解法1

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


using namespace std;

const int N = 110, M = (1 << 10) + 5;

int n, m;
int a[N];
int cnt[M], tot;
int f[2][M][M];


bool check (int s) {
    return !(s & (s << 1 | s << 2 | s >> 2 | s >> 1));
}


int main () {
    cin >> n >> m;
    char str[15];
    for (int i = 1; i <= n; i++) {
        scanf ("%s", str + 1);
        for (int j = 1; j <= m; j++) {
            if (str[j] == 'P') continue;
            a[i] |= (1 << (j - 1));
        }
    }

    vector <int> state;

    for (int i = 0; i < (1 << m); i ++) {
        if (check (i)) {
            state.push_back (i);
            cnt[i] = __builtin_popcount (i);
        }
    }
    tot = state.size ();
    cout << tot << endl;
    for (int i = 1; i <= n + 2; i ++) {
        for (int s1 = 0; s1 < tot; s1++) {
            for (int s2 = 0; s2 < tot; s2++) {
                int x = state[s1], y = state[s2];
                f[i & 1][y][x] = 0;
                for (int s3 = 0; s3 < tot; s3++) {
                    int z = state[s3];
                    if (a[i] & x) continue;
                    if (x & y || y & z || x & z) continue;
                    f[i & 1][y][x] = max (f[i & 1][y][x], f[(i - 1) & 1][z][y] + cnt[x]);
                }
            }  
        }
    }
    
    cout << f[(n + 2) & 1][0][0] << endl;

    return 0;
}

解法2

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

using namespace std;

const int N = 110, M = 15;

int n, m;
int f[N][60000], pre[M], cur[M], pw[M];
char a[N][M];

void ten_thr (int x, int *a) {
    for (int i = 0; i < m; i ++) {
        a[i] = x % 3;
        x /= 3;
    }
}

int thr_ten (int *a) {
    int res = 0;
    for (int i = 0; i < m; i ++) {
        res += a[i] * pw[i];
    }
    return res;
}

void dfs (int i, int j, int cnt, int state) {
    f[i][state] = max (f[i][state], cnt);
    if (j >= m) return;
    if (!cur[j] && !pre[j] && a[i][j] != 'H') {
        cur[j] = 2;
        dfs (i, j + 3, cnt + 1, thr_ten (cur));
        cur[j] = 0;
    }
    dfs (i, j + 1, cnt, state);
}


int main () {

    cin >> n >> m;
    pw[0] = 1;
    for (int i = 1; i <= m; i ++) {
        pw[i] = pw[i - 1] * 3;
    }
    for (int i = 1; i <= n; i ++) {
        scanf ("%s", a[i]);
    }
    memset (f, -1, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j < pw[m]; j ++) {
            if (f[i - 1][j] < 0) continue;
            ten_thr (j, pre);
            for (int k = 0; k < m; k ++) {
                cur[k] = pre[k] ? pre[k] - 1 : 0;
            }
            int state = thr_ten (cur);
            dfs (i, 0, f[i - 1][j], state);
        }
    }
    int ans = 0;
    for (int i = 0; i < pw[m]; i ++) {
        ans = max (ans, f[n][i]);
    }
    cout << ans << endl;

    return 0;
}
posted @ 2025-10-09 21:19  michaele  阅读(6)  评论(0)    收藏  举报