POJ - 1185 炮兵阵地(状压DP)

题目: 司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
取自POJ 1185
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

思路: 我感觉是一道中等的状压DP题, 如果读者刚了解到状压DP的话,可以先尝试状压DP入门题后,再回来看这道题.根据题意,一个炮兵所处的位置可能会影响到上下左右四个方向两格的距离,我们先考虑单独一行,将一行内不满足的状态排除,再讨论每行之间的情况,而且我们可以把上下两行内不能有同列含有炮兵看做每一行与上两行没有同列炮兵,则每一行只需考虑上两行的状态,所以我们动态方程应该是三维的,即DP[i][j][k] 表示在i 行的状态为j,上一行状态为K的最大炮兵数量。
具体操作请看代码

int n,m;
string s;
int mn[110];
long long dp[110][220][220];
int num[1<<13];
int vis[110][220];
int sizes[110];

int getsum(int x) {//获得该状态的炮兵数
	int res = 0;
	while(x>0) {
		if(x&1) res++;
		x>>=1;
	}
	return res;
}

int main() {
	cin>>n>>m;
	for(int i=0;i<=(1<<m);i++) {//先把所以状态的炮兵数算出来
		num[i]=getsum(i);
	}
	for(int i=0;i<n;i++) {
		cin>>s;
		int tmp=0;
		for(int j=0;j<m;j++) {//用二进制思想构造
			if(s[j]=='P') tmp =(tmp<<1)+1;
			else {
				tmp <<=1;
			}
		}
		mn[i] = tmp;
		int now = 0;
		for(int j=0;j<=mn[i];j++) {
			if((j&mn[i])!=j) continue;//剔除在本身不能放炮兵的位置放炮兵的状态
			if((j&(j<<2))||(j&(j<<1))||(j&(j>>2))||(j&(j>>1))) continue;//炮兵直接会误伤的状态
			vis[i][now++]=j;//将该状态存入数组,因为直接用状态值开不了DP的三维数组,我们只能这样优化
		}
		sizes[i]=now;
	}
	for(int i=0;i<n;i++) {//从第一行开始
		for(int j=0;j<sizes[i];j++) {
			if(i==0) {
				dp[i][j][0]=num[vis[i][j]];//i==0时不用考虑前几行的状态
			}
			else if(i==1) {//i==1时只用考虑上一行的状态
				for(int k = 0;k<sizes[i-1];k++) {
					if((vis[i-1][k]&vis[i][j])!=0) continue;
					dp[i][j][k]=num[vis[i][j]]+dp[i-1][k][0];
				}
			}
			else {//其他情况要考虑上两行的状态了
				for(int k = 0;k<sizes[i-1];k++) {//枚举上一行的状态
					if((vis[i-1][k]&vis[i][j])!=0) continue;//如果上一行的状态会被本行误伤,过滤掉
					for(int l = 0;l<sizes[i-2];l++) {
						if((vis[i-2][l]&vis[i][j])!=0) continue;//如果上二行的状态会被本行误伤,过滤掉
						if((vis[i-2][l]&vis[i-1][k])!=0) continue;//如果上二行的状态会被上一行误伤,过滤掉
						dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][l]+num[vis[i][j]]);//选择最大值
					}
				}
			}
		}
	}
	long long ans = 0;
	for(int i=0;i<sizes[n-1];i++) {
		if(n==1) {//只有一行
			ans = max(ans,dp[n-1][i][0]);
		}
		else {//其他情况
			for(int j = 0;j<sizes[n-2];j++){
				ans = max(ans,dp[n-1][i][j]);
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

可以看出,这道题的难点在于代码的实现,本身的思维难点并不大,可以练习位运算的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值