31 NOI2001 炮兵阵地 题解
炮兵阵地
题面
将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地( H
),也可能是平原( P
),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

在满足炮兵部队不能相互攻击的条件下,最多能够摆放多少炮兵部队。
求出最多能摆放的炮兵部队数量
\(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表示当前行的这个位置放了炮兵
那我们在放一个炮兵的时候一定是形如这样的(先只考虑前面的行,因为我们从前往后填)
我们可以将中间 0 的位置改为 2
从而变成这样
我们转移的时候发现好像不太好转移,所以我们利用 \(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;
}