NOI2001 炮兵阵地 (状压dp)

Posted fang-hao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NOI2001 炮兵阵地 (状压dp)相关的知识,希望对你有一定的参考价值。

传送门

Luogu

题目描述

司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

技术分享图片

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

 

思路

首先可以看出这是一道状压dp,然后具体思考如何进行状压,首先可以想到用四进制表示离中心的位置,然后每一行向下扫,向下扫的时候利用四进制数进行状态转移,如11表示离中心距离为3,是可以放的,10表示2,10表示1,00表示中心,但是仔细想想会发现这种做法无论在时间,空间,还是代码实现难度上都是难以实现的,所以就要换一种思路考虑。

多谢瑞屎爷的帮助,才过了这道题2333333

其实这道题用二进制就可以了,用1表示这个位置上放了炮兵。我们可以观察到,每个炮兵的影响位置有两个,所以我们要枚举前两行的状态,我们一横行一横行向下扫,用1表示这个这个位置放了炮兵。

dp[i][j][k]表示当前选到了第i行,当前的状态为j时,前一行的的状态为k时的最大数量,所以有4重循环,由上一行枚举到这一行。

注意

这道题如果直接写状压的的话,状态会非常多,数组会炸,空间也会炸O(∩_∩)O。我们发现,同一行至少间隔两个格子才能放一个炮兵,所以允许的状态要比原有的少很多,我们首先预处理出所有的状态,发现只有不到100种,好像是88种╮(╯▽╰)╭

所以时间上不会爆炸233333

用vector数组记录一下所有的情况。

在枚举的时候,还要注意到以下几点

1.要判断每个状态能否放置在地图上

2.要判断每个状态的炮兵能否互相打到

3.用来转移的前一种状态是否存在

代码

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<vector>
 6 using namespace std;
 7 vector<int>v;
 8 int N,M;
 9 bool map[200][200];
10 int check[300];
11 int dp[200][200][200];
12 int val[300];
13 inline bool judge (int a){    //判断这种状态自己行内的炮兵能否互相打到
14     if((a&(a<<1)))return false;
15     if((a&(a<<2)))return false;
16     else return true;
17 }
18 inline int calc(int x){      //计算每种情况的炮兵个数
19     int ans=0;
20     while(x){
21         if(x&1)ans++;
22         x=x>>1;
23     }
24     return ans;
25 }
26 int main(){
27     memset(dp,-1,sizeof(dp));
28     cin>>N>>M;
29     int tot=(1<<M)-1;
30     int shu=0;
31     for(register int i=0;i<=tot;i++){          //预处理出所有可能的状态并用vector数组记录
32         if(judge(i)){
33             v.push_back(i);                    //用vector数组记录情况
34             val[shu]=calc(i);                  //用val数组记录当前情况有多少个炮兵
35             shu++;
36         }
37     }
38     char temp;
39     for(register int i=1;i<=N;i++){
40         for(register int j=1;j<=M;j++){
41             cin>>temp;
42             if(temp==P)map[i][j]=1;
43             else map[i][j]=0;
44             check[i-1]+=(map[i][j]<<(j-1));     //生成地图的二进制形式,如果为1,则可以放炮兵
45         }
46     }
47     int cnt=v.size()-1;
48     for(register int i=0;i<=cnt;i++){
49         if((v[i]|check[0])==check[0]){
50             dp[0][i][0]=val[i];               //初始化dp数组,第0列不受前面列的影响,因此每种状态的初始值就是这种状态的炮兵数量
51         }
52     }
53     for(register int i=1;i<N;i++){           //枚举行
54         for(register int j=0;j<=cnt;j++){          // 枚举当前状态
55             if((v[j]|check[i])!=check[i])continue;    //判断当前的状态能否在地图上放置
56             for(register int l=0;l<=cnt;l++){     //枚举上一行的状态
57                 if(v[l]&v[j])continue;            //判断前一行的炮兵能否打到当前行的炮兵
58                 if((v[l]|check[i-1])!=check[i-1])continue;     //判断前一行的炮兵能否放在地图上
59                 for(register int ll=0;ll<=cnt;ll++){   // 枚举上一行的上一行的状态
60                     if(dp[i-1][l][ll]==-1)continue;      //判断上一种情况是否存在
61                     if(v[ll]&v[j])continue;              //判断两行前的炮兵会不会达到当前行的炮兵
62                     if(v[ll]&v[l])continue;              //判断两行前的炮兵会不会达到上一行的炮兵
63                     if((v[ll]|check[i-2])!=check[i-2])continue;     //判断两行前的炮兵能否放置在地图上
64                     dp[i][j][l]=max(dp[i-1][l][ll]+val[j],dp[i][j][l]);    //状态转移
65                 }
66             }
67         }
68     }
69     int ans=0;
70     for(register int i=0;i<=cnt;i++){
71         for(register int j=0;j<=cnt;j++){
72             ans=max(dp[N-1][i][j],ans);   //因为要求最大值,所以枚举最后一行的所有状态的人数,取最大值
73         }
74     }
75     cout<<ans<<endl;
76 }

 

以上是关于NOI2001 炮兵阵地 (状压dp)的主要内容,如果未能解决你的问题,请参考以下文章

NOI2001 炮兵阵地 (状压dp)

状压DP NOI2001 炮兵阵地

(状压dp)NOI 2001(POJ 1185) 炮兵阵地

NOI2001炮兵阵地

[NOI2001]炮兵阵地

NOI2001炮兵阵地