题目链接:POJ 1185 炮兵阵地
题目
Description
司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"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
题目解析
做这题之前建议先去看一下POJ-3245 Corn Fields,那题思路和这题是一样的,但是那道简单得多,先做完那道做这道会感觉容易一些,附上那道的博客:POJ-3245 Corn Fields 状压dp。
算是进阶的状压dp吧,这里的思路其实不难想,关键是怎么样不TLE或MLE。这里的思路其实算是状压dp一个比较常用的优化:筛选可行状态,什么意思呢?我们在这个具体例子里看。
题意很简单,一个网格图,有些地方可以放兵,有些地方不可以,每个兵的上下左右两格不能有其他兵。问最多可以放多少个兵。
看到
m
≤
10
m \leq 10
m≤10的时候基本上脑子里应该有状压dp的概念了,问题在于,这题自上而下更新的时候,上面两行都需要考虑,也就是说需要一个三维的
d
p
dp
dp数组,而且总时间复杂度为
n
∗
(
2
m
)
3
n*(2^{m})^{3}
n∗(2m)3,妥妥的TLE。那么怎么办呢?仔细思考一下,由于左右也是有限制的,那么我们状压后的1024种状态里,有很多是不符合要求的,比如
5
(
101
)
,
12
(
1100
)
5(101),12(1100)
5(101),12(1100),而实际可行的状态只有不到60种。可不可以把这些根本没用的状态全部去掉,这样时间和空间不就省下来了吗?
确实如此,我们预处理一个数组,把所有可行的状态存进去,这样,我们每次只需要遍历这几个状态就完事了,时间和空间全都省出来了。
顺便一提,注意
n
=
1
n=1
n=1的情况,因为这题的动态规划部分其实是从
i
=
3
i = 3
i=3开始的,那么
n
n
n为1其实是需要特殊处理的。
上代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[105][70][70];
int g[105][12];
int tablet[70][2];
char c;
int dex = 0;
bool judge(int s, int i){
if(s&(1<<(i+1))) return false;
if(s&(1<<(i+2))) return false;
return true;
}
bool able(int i, int s)
{
for(int j = 0; j < 10; j++)
if((s&(1<<j)) && !g[i][j]) return false;
return true;
}
void init()
{
memset(g, 0, sizeof(g));
memset(dp, -1, sizeof(dp));
tablet[0][0] = 0, tablet[0][1] = 0;
for(int s = 1; s < (1<<10); s++) {
int c = 0;
bool flag = true;
for(int i = 0; i < 10; i++){
if(s&(1<<i)){
if(!judge(s,i)) {flag = false; break;}
c++;
}
}
if(!flag) continue;
else tablet[++dex][0] = s, tablet[dex][1] = c;
}
}
int main()
{
init();
int n, m, res = 0;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("\n");
for(int j = 0; j < m; j++){
scanf("%c", &c);
if(c == 'P') g[i][j] = 1;
else g[i][j] = 0;
}
}
for(int s = 0; s <= dex; s++)
if(able(1, tablet[s][0]))
dp[1][s][0] = tablet[s][1],
res = max(res, dp[1][s][0]);
if(n == 1) {printf("%d\n", res);return 0;}
for(int s = 0; s <= dex; s++){
if(able(2, tablet[s][0])){
for(int i = 0; i <= dex; i++){
if(dp[1][i][0] < 0) continue;
if(tablet[i][0] & tablet[s][0]) continue;
dp[2][s][i] = dp[1][i][0] + tablet[s][1];
res = max(res, dp[2][s][i]);
}
}
}
if(n == 2) {printf("%d\n", res);return 0;}
for(int i = 3; i <= n; i++){
for(int s = 0; s <= dex; s++){
if(tablet[s][0] >= (1<<m)) break;
if(!able(i, tablet[s][0])) continue;
for(int j = 0; j <= dex; j++){
if(tablet[s][0] & tablet[j][0]) continue;
for(int k = 0; k <= dex; k++){
if(dp[i-1][j][k] < 0) continue;
if(tablet[s][0] & tablet[k][0]) continue;
dp[i][s][j] = max(dp[i][s][j], dp[i-1][j][k] + tablet[s][1]);
res = max(res, dp[i][s][j]);
}
}
}
}
printf("%d\n", res);
}