POJ 1185 - 炮兵布阵

                                    炮兵阵地

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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值