题目地址:
https://www.acwing.com/problem/content/294/
司令部的将军们打算在
N
∗
M
N*M
N∗M的网格地图上部署他们的炮兵部队。一个
N
∗
M
N*M
N∗M的地图由
N
N
N行
M
M
M列组成,地图的每一格可能是山地(用'H'
表示),也可能是平原(用'P'
表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围是其四个方向
2
2
2格及以内。如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式:
第一行包含两个由空格分割开的正整数,分别表示
N
N
N和
M
M
M;接下来的
N
N
N行,每一行含有连续的
M
M
M个字符('P'
或者'H'
),中间没有空格。按顺序表示地图中每一行的数据。
输出格式:
仅一行,包含一个整数
K
K
K,表示最多能摆放的炮兵部队的数量。
数据范围:
N
≤
100
,
M
≤
10
N≤100,M≤10
N≤100,M≤10
思路是动态规划,可以用状态压缩存每行的炮兵放的状况,即用一个整数的二进制位来存, 1 1 1表示有炮兵, 0 0 0表示没炮兵。设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为只摆前 i i i行,第 i − 1 i-1 i−1行的状态是 j j j,第 i i i行的状态是 k k k的情况下,最多能放多少个炮兵。那么可以按照第 i − 2 i-2 i−2行是怎么放的来分类,则有: f [ i ] [ j ] [ k ] = max p ∧ j { f [ i − 1 ] [ p ] [ j ] + c ( k ) } f[i][j][k]=\max_{p\land j}\{f[i-1][p][j]+c(k)\} f[i][j][k]=p∧jmax{f[i−1][p][j]+c(k)}其中 c ( k ) c(k) c(k)是 k k k的二进制表示里 1 1 1的个数,并且 p p p遍历所有与 j j j无矛盾的合法状态。可以先预处理出来所有的行的合法状态,并预处理每个状态的 1 1 1的个数。空间方面可以用滚动数组优化。代码如下:
#include <iostream>
#include <vector>
using namespace std;
const int N = 11, M = 1 << 10;
int n, m;
// 存输入,g[i]的各个二进制位表示第i行的地理情况,1表示有山,0表示无山
int g[110];
// 存行的合法状态
vector<int> state;
int f[2][M][M];
int cnt[M];
// 判断st是否是行合法状态
bool check(int st) {
for (int i = 0; i < m; i++)
if ((st >> i & 1) && ((st >> i + 1 & 1) || (st >> i + 2 & 1)))
return false;
return true;
}
// 存st的二进制表示里有多少个1
int count(int st) {
int res = 0;
while (st) {
st -= st & -st;
res++;
}
return res;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 0; j < m; j++) {
char x;
cin >> x;
if (x == 'H') g[i] += 1 << j;
}
for (int i = 0; i < 1 << m; i++)
if (check(i)) {
state.push_back(i);
cnt[i] = count(i);
}
for (int i = 1; i <= n; i++)
for (int j = 0; j < state.size(); j++)
for (int k = 0; k < state.size(); k++)
for (int u = 0; u < state.size(); u++) {
// 如果在枚举第1行,那么此时前两行必须全是0才有意义
if (i == 1 && u != 0 && j != 0) continue;
int a = state[j], b = state[k], c = state[u];
// 如果两行之间有冲突,则略过
if ((a & b) | (b & c) | (a & c)) continue;
// 炮兵放在山上的情形略过
if (g[i - 1] & a | g[i] & b) continue;
f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
}
int res = 0;
for (int j = 0; j < state.size(); j++)
for (int k = 0; k < state.size(); k++)
res = max(res, f[n & 1][j][k]);
cout << res << endl;
return 0;
}
时间复杂度 O ( n 2 3 n ) O(n2^{3n}) O(n23n),但是实际没有这么高,因为行合法的状态数量远远小于 2 n 2^n 2n,空间 O ( n 2 2 n ) O(n2^{2n}) O(n22n)。