Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 13278 | Accepted: 4868 |
Description
Input
Output
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(); }