【洛谷 2704】【NOI2001】炮兵阵地【状态压缩】

题目描述
司令部的将军们打算在 N ∗ M N*M NM的网格地图上部署他们的炮兵部队。一个 N ∗ M N*M NM的地图由N行 M M M列组成,地图的每一格可能是山地(用 “ H ” “H” H 表示),也可能是平原(用 “ P ” “P” P表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
在这里插入图片描述

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


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

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


输入 #1
5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出 #1
6


解题思路
除了for循环打的头痛。。。(使劲点头)
我们来康康这道题
首先,这是一道状压 D P DP DP,因为数据量比较状压。。。(呵呵哒,我就看不出)
分别有 N ∗ M N*M NM的地图,有缺陷的地形以及十字形的覆盖范围。

如下是 D P DP DP思考过程:

  1. 定义。。我们需要考虑定义,我们可以定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示到第 i i i行状态为 k k k,且上一行状态为 j j j时的最大方案数
  2. 初始化。。然后我们要来考虑初始化,因为状态肯定由前两行推过来,所以我们需要单独处理第一二行的方案数
  3. 转移方程。。取最大的话就一定要和 原来的自己、前一个状态+增长 比较,取较大的那个.

那么有了这个 d p dp dp 方程后,就可以愉快的递推了,不过这道题有几个细节需要注意一下:

  1. 判断每个状态有没有两个炮兵左右距离在两格之内:
    这个需要动脑想一下,我们发现一个神奇的结论,如果把表示当前状态的二进制数位运算左移一位,那么用这个结果与原状态做一次位运算与操作,如果结果不是 0,那么就一定存在两个炮兵左右距离在一格之内。同理,左移两位就可以判断左右距离在两格之内。这个过程也就是 i i i& ( i < < 1 ) (i<<1) (i<<1) i i i& ( i < < 2 ) (i<<2) (i<<2)
  2. 判断每一列之前两行有没有炮兵:
    这个就直接用当前状态分别与之前的两行即可,就是 s [ r ] s[r] s[r]& s [ t ] , s [ t ] s[t],s[t] s[t]s[t]& s [ j ] s[j] s[j],如果与操作结果不为零,说明有若干列前两行有炮兵,也就是说当前状态不合法。

代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,num;
int bsy,f[102][1005][1005],s[1005],c[1005],a[103];
char ma[103];
int get(int x)//计算某一状态含有多少个1(即有多少个炮兵) 
{
	int e=0;
	while(x>0)
	{
		++e;
		x-=x&(-x);
	}
	return e;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)//读入地图,将山地(不能放兵)的地方设为1 
	{
		scanf("%s",ma);
		for(int j=0;j<m;++j)
		{
			if(ma[j]=='H')
				a[i]+=1<<j;
		}
	}
	for(int i=0;i<=(1<<m)-1;++i)//枚举所有的状态 
	{
		if(((i&(i<<1))==0)&&((i&(i<<2))==0)&&((i&(i>>1))==0)&&((i&(i>>2))==0))//判断每个状态有没有两个炮兵左右距离在两格之内,即判断此状态是否正确。
		{
			++num;
			s[num]=i;
			c[num]=get(i);
			if((i&a[1])==0)
				f[1][0][num]=c[num];//初始化第一行 
		}	
	}
	//初始化第二行 
	for(int i=1;i<=num;++i)//枚举第一行状态 
	{
		for(int j=1;j<=num;++j)//枚举第二行状态 
		{
			if(((s[i]&s[j])==0)&&((a[2]&s[j])==0))//判断是否与地形和第一行冲突 
				f[2][i][j]=max(f[2][i][j],f[1][0][i]+c[j]);
		}
	}
	//dp过程 
	for(int i=3;i<=n;++i)//枚举当前行数 
		for(int j=1;j<=num;++j)//枚举当前行状态
			if((a[i]&s[j])==0)//不与地形冲突 
				for(int r=1;r<=num;++r)//枚举前一行状态
					if((s[r]&s[j])==0) //当前行状态不与前一行冲突 
						for(int t=1;t<=num;++t)//枚举前两行状态
							if(((s[r]&s[t])==0)&&((s[t]&s[j])==0)) //不与前两行冲突,且前两行自身不冲突 
								f[i][r][j]=max(f[i][r][j],f[i-1][t][r]+c[j]);
	for(int i=1;i<=num;++i)//枚举最后两行为结尾的情况,统计答案
		for(int j=1;j<=num;++j)
			bsy=max(bsy,f[n][i][j]);
	printf("%d\n",bsy);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值