题目大意
司令部的将军们打算在
N
∗
M
N*M
N∗M的网格地图上部署他们的炮兵部队。一个
N
∗
M
N*M
N∗M的地图由
N
N
N行
M
M
M列组成,地图的每一格可能是山地(用"
H
H
H" 表示),也可能是平原(用"
P
P
P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
Output
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
Sample Output
6
思路
看了题一种状态压缩的感觉涌上心头。为什么呢?而m即一行的个数小于等于10,每个格子上只有放或不放两种情况
很自然就会想到状压DP
还有一点很重要::
要符合题目条件的 只有平原可以放炮兵。
所以还要匹配 炮兵放法 与 平原 的关系
如下是DP思考过程::
- 定义。。我们需要考虑定义,我们可以定义dp[i][j][k]表示到第i行状态为j,且上一行状态为k时的最大方案数
- 初始化。。然后我们要来考虑初始化,因为状态肯定由前两行推过来,所以我们需要单独处理第一二行的方案数
- 转移方程。。取最大的话就一定要和 原来的自己、前一个状态+增长 比较,取较大的那个
首先我们一定有这样一个for循环
for (int i = 3; i < N; i++) {//枚举到第i行
//状态转移条件
for (int k = 0; k < cnt; k++) {//枚举第i行的所有状态
//状态转移条件
for (int m = 0; m < cnt; m++) {//枚举第i-1行的所有状态
//状态转移条件
for (int n = 0; n < cnt; n++) {//枚举第i-2行的所有状态
//状态转移条件
dp[i][k][m] = max(dp[i][k][m], dp[i - 1][m][n] + num[k]);
}
}
}
}
其中 n u m [ s ] num[s] num[s]数组是我们预处理的,记录状态 s s s一共安排了多少炮兵(其实就是二进制位1的个数)。 c n t cnt cnt是所有状态的总数,和 M M M的值有关。可以看到里面还有状态转移条件并没有处理,我们有什么状态转移条件呢?
- 安排的炮兵不能在山丘上——解决方案:输入时将每一行地形记录在数组
mmap
中,比如一行地形如果是“PHPP”
,那么我们的mmap存储的是“0100”
,当我们判断一个状态有没有和地形冲突时,只需要将该状态与地形按位与,如状态1011
,1011&0100=0
,说明有山丘的地方都没有炮兵!
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> s;
if (s == 'H') mmap[i] |= 1 << j;
}
}
- 那么还有一个问题,就是炮兵的自相残杀问题,如何判断两个状态之间的炮兵射程不重叠,比如第一行是
1011
第二行是0100
(假设地形合格),同样按位与,如果是0就没有位置上的覆盖,所以第i-1
行要判断与第i
行是否伤害,第i-2
行要判断与前两行是否互相伤害。
那么我们就补全了for循环
for (int i = 3; i < N; i++) {//枚举到第i行
for (int k = 0; k < cnt; k++) {//枚举第i行的所有状态
//状态k与i的地形冲突
if (S[k] & mmap[i]) continue;
for (int m = 0; m < cnt; m++) {//枚举第i-1行的所有状态
//状态m与i-1的地形冲突或者与i行互相伤害
if ((S[m] & mmap[i - 1]) || (S[m] & S[k])) continue;
for (int n = 0; n < cnt; n++) {//枚举第i-2行的所有状态
//状态n与i-2的地形冲突或者与i-1行互相伤害,或者与i行互相伤害
if ((S[n] & mmap[i - 2]) || (S[n] & S[m]) || (S[n] & S[k])) continue;
dp[i][k][m] = max(dp[i][k][m], dp[i - 1][m][n] + num[k]);
}
}
}
}
你可能已经看到了,我们并没有考虑一行之中是否互相伤害的问题,接下来我们就解决这个问题,对于状态数组S
,我们在程序的开始就预处理出所有的可用状态(一行之间冲突的状态),并统计他的数目cnt
,这样我们不仅可以在后面避免一行之内的冲突,而且简化了状态的数目。count1
统计i
的二进制数1的个数
S[0] = 0;
for (int i = 1; i < (1 << M); i++) {
if (i&(i << 1) || i & (i >> 1))continue;//左右有相邻元素
if (i&(i << 2) || i & (i >> 2))continue;//左2右2有相邻元素
S[cnt] = i;
num[cnt++] = count1(i);
}
值得注意的是,主for
循环我们是从第三行开始的,第一二行要单独进行处理
for (int k = 0; k < cnt; k++) {
if (S[k] & mmap[0])continue;//地形冲突
dp[0][k][0] = num[k];
}
for (int k = 0; k < cnt; k++) {//第一行
if (S[k] & mmap[1]) continue;
for (int m = 0; m < cnt; m++) {//第零行
if ((S[k] & mmap[0]) || (S[k] & S[m])) continue;
dp[1][m][k] = max(dp[1][m][k], dp[0][k][0] + num[m]);
}
}
最后只需要在dp
数组的最后一行进行统计,找出最大的那个元素值即可。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define ll int
#define MAX 15
//cnt:可用状态的总数 S:所有的可用状态
ll N, M, cnt = 1, S[1025];
//dp:动归属组;num:每一种状态含有的炮兵数目;mmap:每一行对应的地形
ll dp[105][70][70], num[1025], mmap[105];
int count1(int k) {
int ans = 0;
while (k > 0) {
ans++;
k -= k & (-k);//k&(-k):k的最后一位1对应的十进制数
}
return ans;
}
int main() {
cin >> N >> M; char s;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> s;
if (s == 'H') mmap[i] |= 1 << j;
}
}
S[0] = 0;
for (int i = 1; i < (1 << M); i++) {
if (i&(i << 1) || i & (i >> 1))continue;//左右有相邻元素
if (i&(i << 2) || i & (i >> 2))continue;//左2右2有相邻元素
S[cnt] = i;
num[cnt++] = count1(i);
}
for (int k = 0; k < cnt; k++) {
if (S[k] & mmap[0])continue;//地形冲突
dp[0][k][0] = num[k];
}
for (int k = 0; k < cnt; k++) {//第一行
if (S[k] & mmap[1]) continue;
for (int m = 0; m < cnt; m++) {//第零行
if ((S[m] & mmap[0]) || (S[k] & S[m])) continue;
dp[1][k][m] = max(dp[1][k][m], dp[0][m][0] + num[k]);
}
}
for (int i = 2; i < N; i++) {//枚举到第i行
for (int k = 0; k < cnt; k++) {//枚举第i行的所有状态
//状态k与i的地形冲突
if (S[k] & mmap[i]) continue;
for (int m = 0; m < cnt; m++) {//枚举第i-1行的所有状态
//状态m与i-1的地形冲突或者与i行互相伤害
if ((S[m] & mmap[i - 1]) || (S[m] & S[k])) continue;
for (int n = 0; n < cnt; n++) {//枚举第i-2行的所有状态
//状态n与i-2的地形冲突或者与i-1行互相伤害,或者与i行互相伤害
if ((S[n] & mmap[i - 2]) || (S[n] & S[m]) || (S[n] & S[k])) continue;
dp[i][k][m] = max(dp[i][k][m], dp[i - 1][m][n] + num[k]);
}
}
}
}
ll res = 0;
for (ll i = 0; i < cnt; i++) {
for (ll j = 0; j < cnt; j++) {
res = max(res, dp[N - 1][i][j]);
}
}
cout << res << endl;
}
当然存储空间还可以进一步优化,为什么这里dp
可以开70*70,是因为我们首先要对状态进行筛选,选出可用的cnt
个,而cnt
不会超过70。当然也可以用滚动数组组对内存进行进一步的优化。