POJ1185炮兵阵地(状压dp)

学了两周的状压dp,终于把这道题a了…
参考的这篇大佬的博客学习的状压
点这里

题意:

给一个N X M的地图,其中P代表平原能放一个炮兵,H代表一个山不能放炮兵,每个炮兵可以打到上下左右相距为2的距离。问在炮兵互不伤害的情况下地图上最多能放多少个炮兵。(N <= 100,M <= 10)

思路:

看到数据范围和显然这是一道状压dp的题目,首先我们把地图处理好,P用0表示,H用1表示,每一行看作一个二进制数,然后我们把每行所有的可行状态dfs出来。然后再dp就可以了
dp[i][j][k]表示第i行第j个状态,第i-1行第k个状态的最大值。

代码:

#include<cstdio>
#include "algorithm"
using namespace std;
#define maxn 1025
int dp[110][maxn][maxn],sta[110][maxn],num[110],lim[110],map[110][110];
//num数组存的是每行可行的状态数 
//sta 存的是每行的可行状态
int n,m;
//dfs搜出每行能放的所有的状态
// c是当前的长度,i表示当前第几行,now是现在的状态
void dfs(int c,int i ,int now){ 
    if(c>m)return ; //如果长度大于m,return,剪枝;
    if(c==m&&(now&lim[i])==0){  //now&lin[i]==0说明对应的二进制位没有1 1,当前状态可行 
        sta[i][num[i]++]=now;
        return ;
    }
    dfs(c+1,i,now<<1);  //下一格不放
    if((now&3)==0)dfs(c+1,i,(now<<1)|1);  //这一行太关键了!!表示当前二进制的最后两位是0,那么我下一位可以放1
}
//这个函数是求当前状态上有多少个1,就是炮兵的人数
int get(int x){
    int ans=0;
    while(x){
        if(x&1)ans++;
        x>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        char s[20];
        scanf("%s",s);
        for(int j=1;j<=m;j++){
            if(s[j-1]=='P')map[i][j]=0;
            else{
                map[i][j]=1;
                lim[i]+=(1<<(m-j));  //lim[i]的二进制上面有1说明这个点不能造
            }
        }
    }
    //for(int i=1;i<=n;i++)printf("%d ",lim[i]);  //每行的限制
    //printf("\n");
    for(int i=1;i<=n;i++)dfs(0,i,0);   
    //for(int i=1;i<=n;i++)printf("%d ",num[i]);
    //printf("\n");
    //for(int i=1;i<=n;i++)
       // for(int j=0;j<num[i];j++)
        //    printf("%d\n",sta[i][j]);
      //  printf("\n");
    int ans=0;
    //dp[i][j][k]表示第i行第j个状态,第i-1行第k个状态得最大值
    //因为炮兵有三行的限制,我们要先把前两行的dp写好。这是第一行
    for(int i=0;i<num[1];i++){  
        dp[1][i][0]=get(sta[1][i]);
        ans=max(ans,dp[1][i][0]);
    }
    //这是第二行
    for(int i=0;i<num[1];i++){
        for(int j=0;j<num[2];j++){
            if((sta[2][j]&sta[1][i])==0){。//保证状态要合法
                dp[2][j][i]=max(dp[2][j][i],dp[1][i][0]+get(sta[2][j]));
                ans=max(ans,dp[2][j][i]);
            }
        }
    }
    //这是从第三行开始
    for(int i=3;i<=n;i++){//
        for(int j=0;j<num[i];j++){  //这是第i行的状态
            for(int k=0;k<num[i-1];k++){  //第i-1行 的状态
                if(sta[i][j]&sta[i-1][k])continue;   //剪枝
                for(int p=0;p<num[i-2];p++){   //第i-1行的状态
                    if((sta[i-1][k]&sta[i-2][p])||(sta[i-2][p]&sta[i][j]))continue;
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+get(sta[i][j]));
                    ans=max(ans,dp[i][j][k]);
                }
            }
        }
    }
    printf("%d\n",ans);
}

/*
 5 4
 PHPP
 PPHH
 PPPP
 PHPP
 PHHP
 */

做状压dp的小心得:

对于像这种每一行上有限制的状压dp,我们可以先搜出这一行所有的可行方案,类似的题目有2018icpc南京网络赛的 AC Changlle(应该是这么拼的)
完毕!