poj 炮兵阵地 状态压缩DP + 位运算

炮兵阵地
Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 13278   Accepted: 4868

Description

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

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


这个题目是一道典型的状态压缩DP 。此题的位运算用到比较多。

状压DP 可以说是 集合上的dp 。每个集合的所包含的元素用二进制表示成一个数字。


由于每行最多有M最大为10个,并且每个位置只有两种状态(也就是 有炮兵 或者没有炮兵),于是用二进制数0-1023 来表示所有的状态。

二进制的10001,就表示在第1 个位置有炮兵,第5 个位置有炮兵的状态,表示成10进制的数就是17 。

因此就可以用 每个Integer类型的数就可以来表示一种状态,把一种状态压缩成了一个值,在通过DP 求解 ,就是状态压缩DP。

1。   首先是表示给出的 平地和高原的这张图 ,每一行的情况用 一个二进制数字表示,每一位表示一个点的情况。把p 看成数字0 ,把h 看成数字1 ,(为什么把h看成1 在后边)

那么第一行的二进制表示   0100 ,在转化为 十进制数就是 4。 于是4 就可以表示第一行的情况,3表示第二行的情况。

2 。   我们是把每一行看成一种状态,在列上做DP ,根据前两行的状态去推出这一行的状态。

    可以用 f 来推出 表示当前的状态的最大炮兵数。具体这个数组该怎么开 就是个问题了。 

    我们可以 f[ r , i , j ] 来表示 当前第 r 行 ,此行状态为 i  ,上一行的状态为 j 时 的最大的 炮兵数。

    f[ r , i , j ] = max (  f[ r-1, j , t  ] + bitnum[ i ]); // bitnum[ i ] 表示 i 的二进制的表达式中包含 1 的个数 。

    i 表示 当前行的状态 , j 表示 上一行的状态 , t 表示 上一行的上一行的状态。

    由于攻击范围是 2 个格子 ,因此  当前行 只会跟上两行有关系 , 就可以    i 状态的最大炮兵数, 跟   j ,t  有关,状态转移的方程   就要包括这三个参数。

    如果只跟 j 有关的话, 就直接可以 f [ r, i ] = max( f[ r -1 , j] + bitnum[ i ])  ;

    而这里 跟三个参数 有关  写 f [ r ,i ] =  operator ( f[ r-1 ,j] , f[r-2,t] ) ; 似乎不可以有 两种状态推出 。

    f[ r , i , j ] = max (  f[ r-1, j , t  ] + bitnum[ i ]);   f[ r , i , j ] 来表示 当前第 r 行 ,此行状态为 i  ,上一行的状态为 j 时 的最大的 炮兵数。

    这样f [ r ,i , j ] 就可以f [ r -1 , j , t ]  推出来,

   当然由上一种状态推出下一种 状态的总炮兵的条件:这样只要 判定  i , j , t 三种状态表示的炮兵不会在攻击范围内 , 并且 当前状态  i 还要 符合地形。

3。 位运算 。 条件的判断 可以用到位运算 。

         i 和 j 状态是否攻击 可以理解为   i 和 j 的相同位上 不同时 为 1 。 也就是 i & j ==0 就是合法状态。

         同时 i 和当前的地形 也不能冲突 ,   由于地形中 不可放炮兵的 用 1 来表示 ,对于地形为 k 时 , 只要  k & i == 0 就是 合法的 。

     

       还有一点 就是 每种 状态是 0 - 1023 中 的一个数 ,但是有些状态 本身就是 不成立的 比如 二进制的 1 011 ,两个1 中间至少有两个 0 ,      

      于是 就可以把所有在行上的合法状态预处理出来,0-1023 之间的合法状态 的总和 为 60左右 ,可以写个程序跑出来。

       在 60 中状态上 dp 要比1024 中状态上DP 效率高很多。

      我们可以将这 60 种状态 在映射一下,

       f[ r , i , j ] = max (  f[ r-1, j , t  ] + bitnum[ i ]);
       i = 1 时 表示 第一种合法的状态,j 表示第j 种 在行上合法的状态。因此就要预处理出所有的的合法状态,还可以预处理出每种合法状态的对应的 二进制中 1 的个数。
   

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int max (int a,int b){
  return (a>b)?a:b;
}
int n ,m; 
int pst [120];  // 表示 第 i 行的 地形  的状态 。。。
int  statenum; // 所有合法的状态的个数
int state[65]; // 存储 每行的  0 - 1023 之间的 所有合法的状态
int bitnum[65];//from 1 --> statenum ;  // 存储 每种合法状态的 二进制表示中的 1 的个数
int f[120][65][65];  // dp 
 int bitsum =0 ; 
bool judge(int num ){// 判断每种状态是否合法, 并且 bitsum 表示 当前状态二进制 中 1 的个数 
     int  last =-3;  // 二进制中 上一个 1 的位置 
     bitsum =0;
     for(int i=0;i<=m;i++){
        if(num>>i&1){
	  bitsum ++;
	  if(i-last<3)  // 如果 位置的差 小于了 3 就是不合法
		  return false;
	  last = i;
	}
     }
   return true;
}
void init(){  //  处理出 所有合法状态 
     int bignum = (1<<m) -1;  
     statenum = 0 ;
    for(int i =0; i<=bignum;i++){
       if(judge(i)){
          statenum++;
	  state[statenum] = i;
          bitnum[statenum] = bitsum;
       }
    }
} 
void solve (){
     memset(f,0,sizeof(f));   
    // dp的 初始 的状态。。。
      for(int  i =1 ;i<= statenum;i++){
         if(!(state[i]&pst[1])){
	     f[1][i][1]= bitnum[i];
	 }
     }
     for(int r =2;r<=n;r++){
        for(int i =1 ;i<=statenum ;i++){
		if(state[i]&pst[r])  // 如果 i 和 第r 行的地形 不相容 
			continue;
	        for(int j =1 ;j<=statenum;j++){
	        	if(state[i]&state[j])  //  i 的状态 和  j 的状态  不相容 
	                	continue;
		         for(int t = 1;t<=statenum;t++){
				 if(state[i]&state[t])
		   			 continue;
				 //  r row   i current pow's state   j表示上一行的状态    t 表示上一行的上一行的状态  
	                         f[r][i][j]=max(f[r][i][j],f[r-1][j][t]+bitnum[i]); 
	                 }
	          }
	}
    } 
    // 枚举所有状态找出最大值
     int ans = 0;
     for(int r =1 ;r<=n;r++){   
	for(int j =1 ;j<=statenum ;j++){
           for(int i =1;i<=statenum;i++){
              ans= max (ans,f[r][j][i]);
	   }
        }
     }    
    printf("%d\n",ans);
}
int main(){
     scanf("%d%d",&n,&m);
     memset(pst,0,sizeof(pst));
     getchar();
     for(int i= 1;i<=n;i++){
         for(int j = 1;j<=m;j++){
	       char  ch  =getchar();
	       pst[i]= pst[i]<<1;
	       if(ch=='H'){
	         pst[i] +=1;
	       }
	 }
	 getchar();
     }
       init();
      solve();
}







你可能感兴趣的:(poj 炮兵阵地 状态压缩DP + 位运算)