POJ1185 炮兵阵地 状压DP经典

传送门:POJ1185

中文题就不说题意了。

网上都说是经典的状压DP,我也是第一次做状压DP的题,但感觉自己深深的被这题奇妙的位运算震慑了。。世界真奇妙。。

奉上一篇讲解的无敌巨明白的解题思路:

状态压缩DP(使用位运算加速)

这是个经典的状态压缩DP,为加深印象详细写写一下报告,由于是中文题目所以不说题意了

思考方法:首先,一个炮的攻击有两行,所以对于第i行来讲,i-1行和i-2行对它有影响,i-3行及以上的都没有影响了,所以我们要得到第i行的信息,只需要知道i-1和i-2的信息(最近有个体会,DP要找到什么因素影响了当前你要求的东西,有影响的我们就处理,没影响的我们不用管)。接着我们就思考怎么表示状态。山用1表示,空地用0表示,空地放了兵也用1表示,那么对于一行,就是一个01的串,这是个二进制数,我们可以想到状态压缩压缩回来一个十进制数。

比如原地图01101011,那么0处可以放兵,所有那么多个0,可以变为1(但也要考虑炮与炮之间不能攻击),要枚举全部情况,我们很自然想到了dfs来枚举,很多解题报告是这样做的,这样确实也能解决,但不是最好的方法。最好的方法是位运算。

要想到位运算,要跳出思维的限制,在一行中,原有的1是固定不能变,炮不能放在山上。0是可以变为1的,但是要保证炮与炮之间不能攻击。要满足这两个要求,我们可以拆开来做。先满足了炮与炮不能互相攻击,然后在这些摆放中再选出跑不在山上的。

只考虑炮的话,枚举量2^10-1,但实际上满足的不足60个,网上有人问过60是怎么计算的,实际上准确的做法我也不确定,但是能大概推出来。一列最多10位,最多其实只能放4个炮,然后接着看3个炮,2个炮,1个炮,0个炮的情况,就可以大致算出。

枚举方法是   for(i=0; i<(i<<col); i++)  if( !(i&(i<<1)) & !(i&(i<<2)) )  i是合法状态   这个是要点,要理解    

int state[MAXM];  保存状态(十进制数),是仅仅满足了炮与炮不互相攻击,但是没有满足炮不在山上

对于一开始的地图,还没放炮,它本身已经表示了一个状态,所以也先压成一个状态,保存在一个数组中

再者,我们得到了一个状态state[i],我们怎么知道这个状态下放了多少炮啊?其实就是判断state[i]这个十进制数变为二进制数后有多少个1,这个要怎么统计呢?位运算!

并且把对应的士兵人数保存在  int soldier[MAXM]; //对应着,在state[i]状态下能放多少个士兵

int base[MAXR];  //第i行的原地图压缩成的一个状态

那么怎么判断炮不在山上呢?  只要state[i] & base[r] = 0 ,就表示state[i]这个状态,可以放在r这行上,而且炮不会在山上,炮之间也不会攻击,这是个要点,理解

 

然后前面说了,i行,i-1行,i-2行的炮会互相影响,他们可能会互相攻击到对方,所以我们假设现在i行,i-1行,i-2行的炮的摆放情况分别是state[i],state[j],state[k]

只有当他们都不两两攻击的时候,这3个状态才能放在一起,否则这3个状态不能放在一起。那么怎么判断他们不会两两攻击呢,方法一样的

state[i] & state[j] = 0   state[i] & state[k] = 0     state[j] & state[k] = 0   ,三个要同时满足,要点,理解

你会发现多次需要用到 一种判断  a&b  是为0还是不为0,所以我们代码中将其写成宏定义方便查看,实际上写成宏后时间慢了一点

#define legal(a,b) (a&b) //判断两个状态共存时是否合法,合法为0,不合法为非0 

 

最后看状态转移方程,设dp[r][i]表示第r行,状态为state[i]是的最大值,

dp[r][i]=max { dp[r-1][j]+dp[r-2][k] } + soldier[i]

也就是第r-1行的状态为state[j],第r-2行的状态为state[k]的和,并加上当前行放了soldier[i]个士兵 , 但要满足state[i],state[j],state[k]不能互相攻击

接着我们可以可以写成三维的形式 dp[r][i][j]= max{ dp[r-1][j][k]} + soldier[i]  ,  dp[r][i][j]表示第r行状态为state[i],第r-1行为state[j]的最大值

以上转自: http://www.cnblogs.com/scau20110726/archive/2013/02/27/2935256.html

我第一次学状压DP看这篇博文都能顺畅看懂,没什么好说的了,给巨巨跪了Orz

然后就是蒟蒻的代码了:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<vector>
#include<map>
#define ll long long
#define pi acos(-1)
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int>P;//dp[r][i][j]=max{dp[r-1][j][k]}+soldier[i]}
int mp[105],state[100],snum[100],dp[105][70][70];//dp[r][i][j]表示第r行状态为state[i],第r-1行为state[j]的最大值
int n,m,cnt;
void init()
{
	cnt=0;
	for(int i=0;i<(1<<m);i++)
	{
		if(!(i&(i<<1))&&!(i&(i<<2)))
		{
			int k=i;
			while(k)
			{
				snum[cnt]+=(k&1);
				k>>=1;
			}
			state[cnt++]=i;
		}
	}
 } 
int main()
{	
	char s[15];
	memset(dp,0,sizeof(dp));
	memset(snum,0,sizeof(snum));
	memset(state,0,sizeof(state));
	memset(mp,0,sizeof(mp));
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
	{
		scanf("%s",s);
		for(int j=0;j<m;j++)
		if(s[j]=='H')
		mp[i]+=(1<<j);
	}
	init();
	//cout<<cnt<<endl;
	for(int i=0;i<cnt;i++)
	{
		if(state[i]&mp[0])
		continue;
		dp[0][i][0]=snum[i];
	}
	for(int i=0;i<cnt;i++)
	{
		if(state[i]&mp[1])
		continue;
		for(int j=0;j<cnt;j++)
		{
			if(state[i]&state[j])
			continue;
			if(state[j]&mp[0])
			continue;
			dp[1][i][j]=max(dp[1][i][j],dp[0][j][0]+snum[i]);
		}
	 }
	 for(int r=2;r<n;r++)
	 {
		for(int i=0;i<cnt;i++)
		{
			if(state[i]&mp[r])
			continue;
			for(int j=0;j<cnt;j++)
			{
				if(state[i]&state[j])
				continue;
				if(state[j]&mp[r-1])
				continue;
				for(int k=0;k<cnt;k++)
				{
					if(state[k]&mp[r-2])
					continue;
					if(state[k]&state[j])
					continue;
					if(state[k]&state[i])
					continue;
					dp[r][i][j]=max(dp[r][i][j],dp[r-1][j][k]+snum[i]);
				}
			}
		}
	 }
	 int ans=0;
	 for(int i=0;i<cnt;i++)
	 for(int j=0;j<cnt;j++)
	 ans=max(ans,dp[n-1][i][j]);
	 printf("%d\n",ans);
    return 0;
}
这题几乎全程使用位运算啊。。世界真奇妙。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值