poj 1185 炮兵阵地 【状压DP】

炮兵阵地
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 21532 Accepted: 8354

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


第二道状态压缩,里面有不少自己的想法,有错误的地方欢迎指正。


思路:状态压缩,把每行压缩成一个状态,用二进制表示,并用数组rec[]记录状态。

dp[i][j][k]表示第i行为状态j且第i-1行为状态k时能够放置的最大数。num[i]表示第i个状态二进制里面1的个数。

得状态转移方程:dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][t] + num[j])。

方程含义:

第i行为状态j且第i-1行为状态k时能够放置的最大数 

= max(自身 , 第i-1行状态为k时且i-2行时状态为t时能够放置的最大数 + 第i行为状态j时能够放置的数目)。


首先预处理代码:

用-1标记该状态不符合条件。

memset(dp, -1, sizeof(dp));//不符合条件的i行和i-1行状态  为-1 
//求第一行  预处理 
for(int i = 1; i <= top; i++)//枚举所有状态
{
	num[i] = count(state[i]); 
	if(two(state[i], rec[1])) 
	{
		for(int j = 1; j <= top; j++)
		dp[1][i][j] = num[i];//第0行状态可以为任意 
	} 
} 

具体更新过程:由i-1行 和 i-2行状态 来推导 i行和i-1


for(int i = 2; i <= N; i++)
{
	for(int j = 1; j <= top; j++)//第i行状态 
	{
		if(!two(state[j], rec[i])) continue;//不能矛盾 
		for(int k = 1; k <= top; k++)//第i-1行状态 
		{
			//if(!two(state[k], rec[i-1])) continue;//k状态和i-1行不能矛盾 
			if(!two(state[k], state[j])) continue;//和第i行状态不能矛盾
			for(int t = 1; t <= top; t++)//i-2行状态 
			{
				if(!two(state[t], state[j])) continue;//和第i行不能矛盾
				//if(!two(state[p], state[k])) continue;//和第i-1行不能矛盾
				if(dp[i-1][k][t] != -1)//i-1行为k状态和i-2行为t状态 是满足条件的
				dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][t] + num[j]);//更新 
			}
		}
	}
} 

可能有人会疑惑:在枚举第i-1行状态时不判断是否和i-1行矛盾,以及枚举第i-2行状态时也不进行类似的判断。

因为这个语句

if(dp[i-1][k][t] != -1)//i-1行为k状态和i-2行为t状态 是满足条件的


AC代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[110][100][100];//dp[i][j][k]表示 第i行在j状态 第i-1行在k状态时 能够放置的最大数 
int rec[110];//记录每行状态 
int state[100];//存储所有状态 
int num[100];//记录每种状态中1的个数 
int top;//总状态数
int N, M;
char str[110][20];
int one(int x)//判断该状态是否有间隔小于3的两个1 
{
	if(x & x<<1) return 0;
	if(x & x<<2) return 0;
	return 1;
} 
int two(int x, int y)//判断两个状态是否有相邻的1 
{
	if(x & y) return 0;
	return 1;
}
int count(int x)//统计x的二进制中1的个数
{
	int cnt = 0;
	while(x)
	{
		cnt++;
		x &= x-1;
	}
	return cnt;
} 
void init()
{
	top = 0;
	int total = 1 << M;
	for(int i = 0; i < total; i++) if(one(i)) state[++top] = i; //找出所有合法状态 
}
int main()
{
	while(scanf("%d%d", &N, &M) != EOF)
	{
		init();
		for(int i = 1; i <= N; i++)
		{
			rec[i] = 0;
			scanf("%s", str[i]);
			for(int j = 0; j < M; j++)
			{
				if(str[i][j] == 'H') 
				rec[i] += 1 << j;//转换二进制 
			}
		}
		memset(dp, -1, sizeof(dp));//不符合条件的i行和i-1行状态  为-1 
		//求第一行  预处理 
		for(int i = 1; i <= top; i++)//枚举所有状态
		{
			num[i] = count(state[i]); 
			if(two(state[i], rec[1])) 
			{
				for(int j = 1; j <= top; j++)
				dp[1][i][j] = num[i];//第0行状态可以为任意 
			} 
		} 
		//更新
		for(int i = 2; i <= N; i++)
		{
			for(int j = 1; j <= top; j++)//第i行状态 
			{
				if(!two(state[j], rec[i])) continue;//不能矛盾 
				for(int k = 1; k <= top; k++)//第i-1行状态 
				{
					//if(!two(state[k], rec[i-1])) continue;//k状态和i-1行不能矛盾 
					if(!two(state[k], state[j])) continue;//和第i行状态不能矛盾
					for(int t = 1; t <= top; t++)//i-2行状态 
					{
						if(!two(state[t], state[j])) continue;//和第i行不能矛盾
						//if(!two(state[p], state[k])) continue;//和第i-1行不能矛盾
						if(dp[i-1][k][t] != -1)//i-1行为k状态和i-2行为t状态 是满足条件的
						dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][t] + num[j]);//更新 
					}
				}
			}
		} 
		int ans = 0;//求解 
		for(int i = 1; i <= top; i++)
		{
			for(int j = 1; j <= top; j++)
			ans = max(ans, dp[N][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、付费专栏及课程。

余额充值