POJ 1185 - 炮兵阵地

来源:互联网 发布:金山数据恢复账号2017 编辑:程序博客网 时间:2024/06/06 14:14
炮兵阵地
Time Limit: 2000MSMemory Limit: 65536KTotal Submissions: 25595Accepted: 9870

Description

司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示: 
POJ 1185 - 炮兵阵地 - 卢凯宾 - 石门实验中学卢凯宾的博客

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。 

Input

第一行包含两个由空格分割开的正整数,分别表示N和M; 
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

Output

仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

Sample Input

5 4PHPPPPHHPPPPPHPPPHHP

Sample Output

6

Source

Noi 01

 

显然这道题目也是状态压缩dp的方向入手,但是相对于之前做的两题更加复杂了。

与这道题比较相似的是种玉米那道题,不同的是这题里面的限制条件比较多,范围也大了一点,但套路其实差不多。

由于一个炮的攻击范围最多可以两行,那么每行的状态就跟前两行有关,因此我们的参数要记录f[i][j][k]表示第i行状态为j,第i-1行状态为k的情况下,该行最多可以摆放多少炮兵部队。

下面,我们分别来考虑炮的攻击范围如何判断。

对于一行内的限制,显然与种玉米是相似的,只不过这里的范围变成了2罢了。设当前状态为x,那么只要判断(x & (x << 1)) | (x & (x << 2))的值即可,如果是1则说明在相邻的三个位里至少有两个1,否则为0,说明这种状态是合法的。

而预先筛选出这些合法的状态可以让每一行的可能状态数从2^m变成不到100种,相当于一个非常有用的剪枝。

同样的,考虑相邻的三行中同一个位置不能有两个1,那么只要三种状态之间互相And一下,若结果都为0则合法。

至于只能在平原上放炮的问题,则更为简单,与种玉米是一样的。

我们注意到题目要求的是最多能放的炮的数量,那么则可以在最初筛选合法状态时顺便统计每种状态中1的数量。

到此,问题迎刃而解。

//poj1185 炮兵阵地(noi2001)
#include <algorithm>
#include <cstdio>
#include <iostream>

using namespace std;

#define lowbit(x) (x&(-x))

const int maxn = 100 + 5;
const int maxm = 10 + 5;
const int maxt = 60 + 5;

int n, m;
int map[maxn];

int num = 0;
int data[maxt], cnt1[maxt];

int f[maxn][maxt][maxt];

//统计x的二进制中有多少个1
int count(int x) {
int res = 0;

while(x) {
++res;
x -= lowbit(x); //每次取最后一个1
}

return res;
}

int main() {
freopen("cannon.in", "r", stdin);
freopen("cannon.out", "w", stdout);
cin >> n >> m;

for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++) {
char ch;
cin >> ch;
map[i] <<= 1;
map[i] |= (ch == 'H'); //不能放的山地设为1
}

int upperLim = 1 << m;

for(int i = 0; i < upperLim; i++)
if((!(i & (i << 1))) && (!(i & (i << 2)))) {
//考虑相邻三位不能有两个1,预先筛选合法状态
data[num] = i;
cnt1[num++] = count(i);
}

//预处理第一行,只用考虑在平原上放
for(int i = 0; i < num; i++)
if(!(data[i] & map[0])) f[0][i][0] = cnt1[i];

//预处理第二行,只用考虑平原和上一行
for(int i = 0; i < num; i++)
if(!(data[i] & map[1]))
for(int j = 0; j < num; j++)
if(!(data[i] & data[j])) f[1][i][j] = max(f[1][i][j], f[0][j][0] + cnt1[i]);

//之后的每行则都要考虑前两行
for(int i = 2; i < n; i++) //第i行
for(int j = 0; j < num; j++) //状态为j
if(!(data[j] & map[i])) //平原上放炮的限制
for(int k = 0; k < num; k++) //第i-1行状态为k
if(!(data[j] & data[k])) //相邻两行同一位置不能同时有炮
for(int l = 0; l < num; l++) //第i-2行状态为l
if((!(data[j] & data[l])) & (!(data[k] & data[l]))) f[i][j][k] = max(f[i][j][k], f[i - 1][k][l] + cnt1[j]);

//各种限制

int ans = 0;

//取最后一行在每种状态下的最大值
for(int i = 0; i < num; i++)
for(int j = 0; j < num; j++)
ans = max(ans, f[n - 1][i][j]);

cout << ans << endl;
return 0;
}

0 0
原创粉丝点击