这道题也是压缩状态的经典题,和铺瓷砖那道题相比,这道题明显难了很多,前前后后花了我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;
}