POJ 1185 炮兵阵地 状压dp

题目链接: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 m10的时候基本上脑子里应该有状压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);
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值