典型的状态压缩动态规划,用位来表示各个状态可以显著加快运算速度、节省存储空间。
对于一个位置,如果它部署了一支部队,那么会对它的前后左右的2格位置产生影响,如果以行作为状态,则不能单单由上一状态转移,那样的话不能保证在它的2格处不发生矛盾,而在这个题目中,以行做状态又很明显,所以在状态的转移时必须是上一行的状态和上上行的状态一起转移,如果用f[i,j,k]来表示在第i行时,取第j个状态(注意是第j个状态),i-1行取的是第k个状态,那么不难得出状态转移方程:
f[i,j,k]=max{f[i,j,k],f[i-1,k,l]+ff[i,j]}
解释一下这个方程i,j,k的含义如上述,l表示在第i-2行取第l个状态,ff[i,j]表示在第i行取第j个状态时在该行放置的多少支部队,由于第二行仅与第一行有关,所以要对第二行进行一下特殊处理,对于第二行,我们有
f[2,j,k]=max{ff[2,j]+ff[1,k]};
这样一来,对动态规划的模型基本处理就完成了,同时我们发现,在m=10时,每一行的状态有2^10种,显然时间和空间都不能承受,必须做出优化,仔细阅读题目,发现如果一个点部署了部队,则其前后两格范围内均不能再部署部队,而且在一个格子为H时,它根本不能部署部队,因此,在许多的状态中,有许多状态是自相矛盾的,根本不需要考虑,可以用某种预处理,把状态提取出来,仅仅储存有用的状态,在这里我的方法是用深度优先搜索预处理一下,try(x:longint)为处理某一行的第x个位置,如果这个位置部署部队,那么x+1,x+2这两个位置都不能放,直接try(x+3)即可,而如果这个地方是山地,就不能放,则try(x+1),即使是平原,也可以不去部署部队,因此还原后也要try(x+1),预处理结束。
对于状态的储存,为了简洁,用一个二进制数来表示,如当m=4时有一种状态为放,不放,不放,放,就用1代表放,0代表不放,这个二进制数为1001,转化为十进制数为9.这样状态的储存问题就迎刃而解了。
接下来,判断状态的矛盾问题,这用到了位运算的知识,1001and0011=0001 这已经很明了了,判断两行间的状态是否矛盾,只需要用(状态1)and(状态2),如果结果是0,说明两个状态不矛盾。
参考 http://www.cnblogs.com/neverforget/archive/2011/10/13/connon.html
#include <vector>
#include <list>
#include <limits.h>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <string.h>
#include <stdlib.h>
using namespace std;
vector<int> pt, num;
int row, col;
int dp[101][62][62];
void get_pt(int c, int cur);
int main(){
string land[105];
int maze[105];
cin>>row>>col;
pt.clear(); num.clear();
for(int i=0; i<row; i++) cin>>land[i];
memset(maze, 0, sizeof(maze));
for(int i=0; i<row; i++){
for(int j=0; j<col; j++){
if(land[i][j]=='H')
maze[i] = maze[i]|(1<<(col-1-j));
}
}
get_pt(0, 0);
//cout<<"size: "<<pt.size()<<endl;
int len = pt.size();
memset(dp, 0, sizeof(dp));
int res = 0;
// for a special case
if(row==1){
for(int i=0; i<len; i++){
if((pt[i]&maze[0])==0)
res += num[i];
}
cout<<res<<endl;
return 0;
}
// for first two line case
for(int i=0; i<len; i++){
if((pt[i]&maze[1])!=0) continue;
for(int j=0; j<len; j++){
if((pt[j]&maze[0])!=0) continue;
if((pt[i]&pt[j])!=0) continue;
dp[1][i][j] = num[i]+num[j];
}
}// end external for loop
// use dynamic programming for remaining rows
for(int i=2; i<row; i++){
// iterate over current row
for(int j=0; j<len; j++){
if((pt[j]&maze[i])!=0) continue;
// iterate over last row
for(int k=0; k<len; k++){
if((pt[k]&maze[i-1])!=0) continue;
if((pt[j]&pt[k])!=0) continue;
// iterate over pre-pre row
for(int p=0; p<len; p++){
if((pt[p]&pt[j])!=0) continue;
if((pt[p]&maze[i-2])!=0) continue;
dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][p]+num[j]);
}
}
}
}
for(int i=0; i<len; i++){
for(int j=0; j<len; j++)
res = max(res, dp[row-1][i][j]);
}
cout<<res<<endl;
return 0;
}
void get_pt(int c, int cur){
if(c >= col){
cur >>= (c-(col-1));
pt.push_back(cur);
num.push_back(__builtin_popcount(cur));
return;
}
// do nothing
get_pt(c+1, cur<<1);
// place
get_pt(c+3, (cur|1)<<3);
}