洛谷 P2704 炮兵阵地 【状压再状压dp】【北大ACM/ICPC竞赛训练】

dp[i][j][k]代表前i层,第i层炮兵布局为j,第i-1层炮兵布局为k的最多摆放炮兵数。我们要记录两层的炮兵摆放,而且转移的时候要枚举第i-2层的状态m,使得m与j相容,m与k相容(当然也保证j与k相容),这样才满足无后效性。只要m满足相容的条件,那怎么从第1层到第i-3层摆放炮兵都无所谓了,因为第i层只能打到i-1层和i-2层,与dp[i-1][j][m]这样的方案数怎么达到的没有关系了。

然后每行最多十列,所以每层的状态有1024种。

dp[100][1024][1024]再加上枚举m的线性转移,炸了。

为什么呢?每一层的状态真的有1024种吗?不是这样的,因为每个炮兵还能打左右两格的距离,所以实际上用二进制数每一位表示有没有炮兵来枚举浪费了计算(比如1110000000是不合法的根本没必要枚举)。我们可以dfs一下算出所有横向相容的状态数,发现10列而且都是平原,也只有60种合法状态。所以我们只考虑这60种就可以了

dp[100][60][60]线性转移,再利用相容枚举的时候剪一剪枝就过了。

 

 1 #include<iostream>
 2 #include<cstring>
 3 using namespace std;
 4 
 5 char maze[105][15];
 6 int status[70],cnt,n,m;
 7 int dp[105][70][70];//dp[i][j][k]第i层布局为j,第i-1层布局为k,前i层的最多摆放炮兵数
 8                 //dp[i][j][k] = dp[i-1][k][枚举m] 使得m与j和k相容 
 9 int rong[70][70],rong2[105][70];
10 int number[70],ans;
11 
12 int num(int s){
13     int count=0;
14     for(int i=1;i<=m;i++){
15         if( s&(1<<i) ) count++;
16     }
17     return count;
18 }
19 
20 void dfs(int col,int last_col,int state){//放到第col列    上一个放炮兵的地方在last_col   status是炮兵摆放的状态 
21     if(col==m+1){
22         status[++cnt]=state;
23         number[cnt]=num(state);
24         return;
25     }
26     if(last_col+3<=col) dfs(col+1,col,state+(1<<col) );//这一列放炮兵 
27     dfs(col+1,last_col,state);//不放炮兵 
28 }
29 
30 int check(int s1,int s2){//s1表示的状态是否与s2表示的状态相容
31     if(rong[s1][s2]!=-1) return rong[s1][s2];
32     int n1=status[s1],n2=status[s2]; 
33     for(int i=1;i<=m;i++){//有没有在第i列上放炮兵
34         if( (n1&(1<<i) ) && (n2&(1<<i)) ) return rong[s1][s2]=0;
35     }
36     return rong[s1][s2]=1;
37 }
38 
39 int check2(int floor,int s){//能不能在第floor层的土地上放s
40     if(rong2[floor][s]!=-1) return rong2[floor][s];
41     int n1=status[s];
42      for(int i=1;i<=m;i++){//有没有在第i列上放炮兵
43         if( (n1&(1<<i) ) && maze[floor][i]=='H' ) return rong2[floor][s]=0;
44     }
45     return rong2[floor][s]=1;
46 }
47 
48 int main(){
49     memset(rong,-1,sizeof(rong));
50     memset(rong2,-1,sizeof(rong2));
51 
52     cin>>n>>m;
53     dfs(1,-2,0);
54 
55     for(int i=1;i<=n;i++)
56      for(int j=1;j<=m;j++) cin>>maze[i][j];
57     
58     //考虑一行中(有m列)全为平地时,能放炮兵的所有合法状态
59     for(int i=1;i<=cnt;i++) {
60         if(check2(1,i)) {
61             dp[1][i][0]=number[i];
62             ans=max(ans,dp[1][i][0]);
63         }
64     }
65     
66     for(int i=1;i<=cnt;i++){
67         if(check2(2,i) ){
68             for(int j=1;j<=cnt;j++){//枚举上一层的状态 
69                 if( check(i,j) && check2(1,j) ) {
70                     dp[2][i][j]=dp[1][j][0]+number[i];
71                     ans=max(ans,dp[2][i][j]);
72                 }
73             }
74         }
75     }
76     //cout<<"!!!"<<endl;
77     for(int i=3;i<=n;i++){
78         for(int j=1;j<=cnt;j++){
79             for(int k=1;k<=cnt;k++){
80                 //dp[i][j][k] == 前i层,第i层摆放为j,第i-1层摆放为k == 最多摆放炮兵的数量 
81                 for(int m=1;m<=cnt;m++){//枚举第i-2层的摆放情况 
82                     if( check(m,j) && check(m,k) && check(j,k) && check2(i-2,m) && check2(i,j) && check2(i-1,k) ) {
83                         dp[i][j][k]=max( dp[i][j][k] , dp[i-1][k][m]+number[j] );
84                         ans=max(ans,dp[i][j][k]);
85                     }
86                 }
87             }
88         }
89     }
90     
91     cout<<ans;
92 
93     return 0;    
94 }

 

posted @ 2018-07-31 20:01  4397  阅读(305)  评论(0编辑  收藏  举报