炮兵阵地
Description
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由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
题目分析
经典的状压DP题目,因为每行的长度不超过10,而且每个位置只有放置士兵和不放置士兵两种操作,自然而然地想到用二进制来表示每一行的士兵放置情况了,也就是说,我们将一行压缩成一个用数字表示的状态,数字在二进制下某个位置的数代表某个位置士兵的放置情况。
然后呢,说一下这个题,由图中我们发现,同一个士兵的攻击范围是上下左右各两个位置,那么在同一行中,不可以布置有相邻和隔着一个位置的士兵,我们记这一行的状态为 x ,如果 x & (x <<1 ) != 0 || x & (x<<2) != 0 ,x<<1可以看作将所有士兵的位置向左移动一个位置,因此,如果 x & (x <<1 ) != 0则说明状态x存在两个相邻的士兵,因此这个状态是不合法的,x << 2 就同理了。
我们注意到,除了第一行不会受到前两行状态的影响(前两行没有士兵),以及第二行只会受到第一行的影响外,其余每一行的状态,也就是士兵的布置情况,都会受到前两行的状态影响,因此我们在布置第 r(r >=2)行的士兵的时候,需要判断r-1,r-2行的状态是否和第r行的状态起冲突,我们记这三行的状态分别为 x,y,z,对应 r,r-1,r-2行,如果 x & y || x & z || y & z ,显然,此时这三行会其冲突,只要这三行的状态不满足上式,此时第r行的状态才可以由前一行转移而来。
总结一下这类题目的一些套路,类似于这个题一样的,如POJ-2411,这类题目的特点都是某一行的状态影响下面x行的状态,那么我们需要预处理出前x行的状态,因为前x行的状态并不是完全受前x行的影响,而x+1行及之后都将受到前x行状态的影响,因此对于x + 1行及以后,判断某状态是否合法需要往上枚举x行,,而我们往往根据x的大小决定dp数组的维度,一般情况下,第一维度表示当前所在行,第二维度表示上一行的状态,第三维度表示上上行的状态.....这样一来我们就可以很方便的判断转移条件了。
代码区
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<cmath>
#include<ctime>
#include<vector>
#include<stack>
#define bug cout << "**********" << endl
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1e8;
const int Max = 1e2 + 10;
int n, m, tot;
int dp[110][Max][Max]; //dp[i][j][k]中,i代表行数,j代表当前行的状态,k代表上一行的状态
int base[Max]; //记录原始地图状态
int state[Max]; //记录同一行中不互相功击的合法状态
int soldier[Max]; //记录和state[i]状态下对应的布置士兵数量
int main()
{
while (scanf("%d%d", &n, &m) != EOF)
{
memset(soldier, 0, sizeof(soldier));
memset(dp, 0, sizeof(dp));
for (int i = 0;i < n; i++) //读入每行地形信息
{
base[i] = 0;
char str[20];
scanf("%s", str);
for (int j = 0; j < m; j++)
{
if (str[j] == 'H') //山地用1表示
{
base[i] += (1 << (m - 1 - j)); //反向存,其实不反向也不错
}
}
}
tot = 0;
for (int i = 0;i < (1 << m); i++) //枚举不互相功击情况下的合法状态
{
if ((i&(i << 1)) || (i&(i << 2)))continue;
//同一行士兵相互功击
int now = i;
while (now != 0) //将当前状态的信息提取,以记录这一状态下代表的士兵数目
{
soldier[tot] += now & 1;
now = now >> 1;
}
state[tot++] = i; //保存合法状态
}
for (int i = 0;i < tot; i++) //预处理第一行
{
if (state[i] & base[0]) continue;
//士兵布置在山上
dp[0][i][0] = soldier[i];
//记录这一状态下对应的士兵数量,第一行的上一个状态为0,说明不受上一行约束
}
for (int i = 0; i < tot; i++) //预处理第二行
{
if (state[i] & base[1])continue;
//第二行的士兵布置在山上
for (int j = 0; j < tot; j++)
{
if (state[j] & base[0])continue;
//第一行士兵布置在山上
if (state[j] & state[i]) continue;
//第一行和第二行士兵相互功击
dp[1][i][j] = max(dp[1][i][j], dp[0][j][0] + soldier[i]);
//在上一行状态的基础下加上当前行状态表示的士兵数目
}
}
for (int r = 2;r < n; r++) //开始处理第三行及之后
{
for (int i = 0; i < tot; i++) //枚举第r行
{
if (state[i] & base[r])continue;
//第r行士兵布置在山上
for (int j = 0;j < tot; j++) //枚举第r-1行
{
if (state[j] & base[r - 1])continue;
//第r-1行士兵布置在山上
if (state[j] & state[i])continue;
//第r行士兵和第r-1行士兵相互功击
for (int k = 0; k < tot; k++) //枚举第r-2行
{
if (state[k] & base[r - 2])continue;
//第r-2行士兵布置在山上
if (state[k] & state[j])continue;
//第r-2行士兵和第r-1行士兵相互功击
if (state[k] & state[i])continue;
//第r-2行士兵和第r行士兵相互功击
dp[r][i][j] = max(dp[r][i][j], dp[r - 1][j][k] + soldier[i]);
}
}
}
}
int ans = 0; //记录最大士兵布置数
for (int i = 0; i < tot; i++)
{
for (int j = 0;j < tot;j++) //枚举最后一行及其前一行的状态,找到使士兵数目最多的状态
{
ans = max(ans, dp[n - 1][i][j]);
}
}
printf("%d\n", ans);
}
return 0;
}