炮兵阵地图文详解NOI2001/POJ1185(状态压缩)

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

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

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

输入样例:
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例:
6

算法思路:
解法一:
因为每个位置能否放置炮兵与它上面两行对应位置上是否放置炮兵有关,所以在向第i行的状态转移时,需要知道第i-1行和第i-2行的状态。我们把每一行的状态看作一个M位二进制数,用一个0~2M-1之间的十进制整数存储,其中第p(0≤p<M)位为1表示该行第p列放置了炮兵,为0则表示没有放置炮兵。

我们在DP前预处理出集合S,存储“相邻两个1的距离不小于3”的所有M位二进制数,这些二进制数代表每一行中两个炮兵的距离不能小于3。

设count(x)表示M位二进制数x中1的个数。

设valid(i, x)表示M位二进制数x属于集合S,并且x中的每个1对应在地图第i行中的位置都是平原。

设F[i,j,k]表示第i行压缩后的状态为j,第i-1行压缩后的状态为k时,前i行最多能摆放多少个炮兵。
在这里插入图片描述
F[i,j,k]表示第i行状态为j,第i-1行状态为k ,那么j&k=0表示这两行的摆放炮兵不冲突,那么k的状态不同,对应的j的状态就不同,所以的枚举j和k。
上一行i-1的状态为k,那么就枚举i-1行上一行的状态l,保证l和k不冲突,再保证l和j不冲突,这样就保证了第i行j和第i-1行k,和第i-2行l都不冲突。

理解了上述,代码就好写了,对于第i行枚举j、枚举k、枚举l三层循环,当然j,k,l属于集合S,并且x中的每个1对应在地图第i行中的位置都是平原。
并且 j&k=0且j&l=0

j&l=0表示这两个整数对应的列没有两个都是1,只有两个都是1按位与才是1.

初值:F[0,0,0]=0,其余为负无穷。
目标:
在这里插入图片描述
完整代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,k;
int s[1005],g[1005];
int f[102][1005][1005],ans;
char ma[103];
int map[103];
int get(int x)//计算某一状态含有多少个1(即有多少个炮兵) 
{
	int e=0;
	while(x>0)
	{
		++e;
		x-=x&(-x);
	}
	return e;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	/*读入地图,将山地(不能放兵)的地方设为1 ,同时把一行地形
	转化为一个m为二进制数所对应的整数。*/
	{
		scanf("%s",ma);
		for(int j=0;j<m;++j)
		  if(ma[j]=='H') map[i]+=1<<j;
	}
	for(int i=0;i<=(1<<m)-1;++i)
	//枚举所有的状态0-2^m-1,处理出满足条件的集合s 
	 if(((i&(i<<1))==0)&&((i&(i<<2))==0)&&((i&(i>>1))==0)&&((i&(i>>2))==0)
/*判断每个1左右各两个是否有1,即判断这种状态是否存在
没有连续的两个1存在,只要移一位后和原来的自己按位与,结果为0,表示正好对应的列
没有两个1同时出现的可能
*/
		{
			++k;
			s[k]=i;
			g[k]=get(i);
			if((i&map[1])==0) f[1][0][k]=g[k];//第一行状态为0,就是没有放置炮兵,第一行的前一行为k
			/*初始化第一行,i时预处理过的整数,map[1]时一行地形
			按位与位0,表示对这的两列不同时为1,符合炮兵摆放规则
			*/} 
	//初始化第二行 
	for(int i=1;i<=k;++i)//枚举第一行状态 
		for(int j=1;j<=k;++j)//枚举第二行状态 
			if(((s[i]&s[j])==0)&&((s[j]&map[2])==0))  f[2][i][j]=max(f[2][i][j],f[1][0][i]+g[j]);//判断是否与地形和第一行冲突 
	//dp过程 
	for(int i=3;i<=n;++i)//枚举当前行数 
		for(int j=1;j<=k;++j)//枚举当前行数状态 
			if((map[i]&s[j])==0)//不与地形冲突 
				for(int p=1;p<=k;++p)//枚举前一行状态 
					if((s[p]&s[j])==0)//当前行状态不与前一行冲突 
						for(int q=1;q<=k;++q)//枚举前两行
						//不与前两行冲突,且前两行自身不冲突 
							if(((s[q]&s[p])==0)&&((s[q]&s[j])==0)) f[i][p][j]=max(f[i][p][j],f[i-1][q][p]+g[j]);
	for(int i=1;i<=k;++i)//枚举最后两行为结尾的情况,统计答案 
		for(int j=1;j<=k;++j)
			ans=max(f[n][i][j],ans);
	cout<<ans;		//输出		
    return 0;
}

预处理后的代码:

#include <iostream>
#include <cstring>
using namespace std;
#define MST(a, b) memset(a, b, sizeof(a));
#define CLR(a) MST(a, 0);
#define rep(x, y, z) for (int x = y; x < z; ++x)
const int INF = 0x3f3f3f3f;
int dp[101][77][77];
int sg[101];
int n, m, idx;
int s[77]; 
int cnt0[77];
int get_one(int x) {
 int cnt = 0;
 while(x) x &= (x-1), ++cnt;
 return cnt;
}
bool ok(int x) {
 if(x & (x << 1)) return false;
 if(x & (x << 2)) return false;
 return true;
}
void init() {
 idx = 0;
 int end = 1 << m;
 rep(i, 0, end) if(ok(i)) {
  s[idx] = i;
  cnt0[idx++] = get_one(i);
 }
}
bool valid(int i, int x) {
 if(sg[i] & x) return false;
 return true;
}
int solve() {
 int ans = 0;
 MST(dp, -1);
 dp[0][0][0] = 0;
 rep(j, 0, idx) if(valid(1, s[j])) {
  dp[1][j][0] = cnt0[j];
  ans = max(ans, dp[1][j][0]);
 }
 rep(i, 2, n+1) {
  rep(j, 0, idx) if(valid(i, s[j])) {
    rep(k, 0, idx) if(valid(i-1, s[k]) && (s[j]&s[k])==0) {
    int last = 0;
     rep(l, 0, idx) if(dp[i-1][k][l] != -1 && (s[l]&s[j])==0 && valid(i-2, s[l])) {
     last = max(last, dp[i-1][k][l]);
    }
    dp[i][j][k] = max(dp[i][j][k], last + cnt0[j]);
    if(i == n) ans = max(ans, dp[i][j][k]);
   }
  }
 }
 return ans;
}
int main(int argc, char const *argv[]) {
 ios::sync_with_stdio(0);cin.tie(0);
 cin >> n >> m;
 rep(i, 1, n+1) rep(j, 0, m) {
  char tmp; cin >> tmp;
  if(tmp == 'H') sg[i] |= (1 << (m-1-j));
 }
 init();
 cout << solve() << endl;
 return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值