POJ.1185.炮兵阵地

这道题也是压缩状态的经典题,和铺瓷砖那道题相比,这道题明显难了很多,前前后后花了我3个小时,实在是智商拙计。

这道题主要运用了状态压缩的方法,在两个地方使用了压缩,首先是地形,其次是炮兵的布置。在输入中先对地形进行处理,还是用二进制法处理,然后转化为十进制压缩保存:

其次是对行内合理炮兵布置进行枚举,行内合理的布置要满足的条件是相邻两个跑步距离要大于两个格,这里要采取一种很神奇的位操作,(temp<<1)&i):观察相邻两格有没有同时出现1(即放置了炮兵),同理(temp<<2)&i)就是观察隔着一格的布置。符合条件就记录下来,同时也是通过位操作计算这种状态下炮兵的数目。

枚举完之后到了最关键的dp环节,首先设置dp数组为:dp[i][j][k],表示第i行的状态是j,第i-1行的状态是k。首先我们来设计状态转移方程,不考虑各种极端情况,首先找出最终输出的结果,因为最优结果没有确定的布置方法,所以状态不应该会固定,只要找出排完最后一行后炮兵数目最大值就可以,也就是找出dp[N][j][k]中的最大值。接着我们想,当我们要得到dp[i][j][k],必然先要得到dp[i-1][k][p],p就是i-2行的状态,也就是排完i-1行后的各种合理状态下对应的炮兵数,那么我们再加上排i行的对应合理状态的炮兵数,同时跟原来的相比选取最大值,就得到dp[i][j][k],也就是:

 

dp[i][j][k]=dp[i-1][k][p]+num[j];

 

其中num[j]是i行的对应合理状态下的炮兵数量

 

状态转换方程得到之后,接下来就来处理极端情况。极端情况就是第0行,第1行,因为这两行是没有i-2行的。第0行时好办,直接把枚举第一行时产生的各种状态的值赋过去。

第1行时,就要进入循环,同时考虑几个方面:本行是否发生地形冲突、上一行是否发生地形冲突,本行和上一行是否发生炮兵冲突。都没有的话就可以进行状态转换,但是参数稍微修改一下。

明显的i-2行不存在,所以上一行是dp[i-1][k][0]。

其余的正常条件就是按照状态转换方程进行判断,而且需要多考虑i-2行与i-1行,i-2行与i行的炮兵冲突。

最后,正如前文指出,求出dp[N][j][k]中的最大值。


非常感谢博主:http://hi.baidu.com/wangxustf/item/9138f80ce2292b8903ce1bc7 为我指点迷津!我的代码框架和思路就是他的思路。整个晚上就在理解他的思想,非常感谢!~


/*
 * POJ1185.cpp
 *
 *  Created on: 2014年7月7日
 *      Author: Prophet
 */
#include<cstdio>
#include<string.h>
const int maxn = 101;
const int maxm = 11;
int main(){
	int m,n;
	char s[maxm];
	int map[maxn];
	int state[64];//列举每种保证行安全的情况
	int num[64];//对应每种行安全排列中炮兵的数目
	int dp[maxn][61][61];
	while(scanf("%d%d",&n,&m)!=EOF){
		for(int i=0;i<n;i++){
			scanf("%s",s);
			for(int j=0;j<m;j++)
				if(s[j]=='H')
					map[i]+=(1<<(m-1-j));//将地图的状态压缩为map数组,0表示平原,1表示山
		}
		//以下函数用于初始化所有的一行内的合法布置(不考虑地形)
		int temp,count=0;//count是计数合法布置的数目
		for(int i=0;i<(1<<m);i++){//列举每种排兵
			temp=i;
			if(((temp<<1)&i)|((temp<<2)&i))//判断行内相邻两个单位都没有炮兵
				continue;
			state[count] = temp;

			num[count] = temp&1;//计算出temp中有多少个1,就是布置了多少炮兵
			/*temp = (temp>>1);
			while(temp){
				num[count]+=temp&1;
				temp = (temp>>1);
			}*/
			while( temp = (temp>>1) )
			     num[count]+=temp&1;

			count++;
		}
		//*******************************************************

		//以下函数用于进行dp
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n;i++){//枚举每一行
			for(int j=0;j<count;j++){//枚举对于i行的所有可能情况
				if(map[i]&state[j]) //若可能情况与地形冲突,直接忽略
					continue;
				if(i==0)//对于第0行,不会出现上一行,因此直接给第0行采用j状态的炮兵数置为这一种状态下的炮兵数
					dp[i][j][0]=num[j];
				else if(i==1){//对于第1行,不会出现i-2行
					for(int k=0;k<count;k++){//枚举i-1行的所有可能情况
						if(map[i-1]&state[k])//同理,先判断i-1行与其地形是否冲突
							continue;
						if(state[j]&state[k])//然后判断本行状态(j)与i-1行(k)是否冲突
							continue;
						if(dp[i][j][k]<dp[i-1][k][0]+num[j])
							dp[i][j][k]=dp[i-1][k][0]+num[j];
					}
				}
				else{
					for(int k=0;k<count;k++){
						if(map[i-1]&state[k]) //同理
							continue;
						if(state[j]&state[k]) //同理
							continue;
						for(int p=0;p<count;p++){//枚举i-2行
							if(map[i-2]&state[p])//检查地形
								continue;
							if((state[k]&state[p]) || (state[j]&state[p]))//判断上下两行(i-2和i-1,i-2和i)的合法状态是否兼容彼此
								continue;
							if(dp[i][j][k]<dp[i-1][k][p]+num[j])
								dp[i][j][k]=dp[i-1][k][p]+num[j];
						}
					}
				}
			}
		}
		//*******************************************************
		//最后输出结果
		int result=0,j,k;
		for(j=0;j<count;j++)
			for(k=0;k<count;k++)
				if(dp[n-1][j][k]>result)
					result = dp[n-1][j][k];
		printf("%d\n",result);
	}
	return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值