[NOI2001]炮兵阵地
题目描述
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。N≤100;M≤10。
输出格式
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
输入输出样例
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
6
【解题思路】
这道题是一道状压 dp 的特别毒瘤的基础题(虽然我打了整整一个早上),但是因为每一个炮兵都会影响到之后的两行的放置,所以用状压去压两行,按行处理每一行的情况即可。每一行放置的时候也很简单,只需考虑这个位置前两行有没有放置炮兵以及这个位置是不是山丘即可。
那么首先,dp 方程可以很快推出来,dp[L][S][i]表示当前状态是 S,上一行的状态是 L,当前考虑到了第 i 行:
dp[L][S][i]=max(dp[L][S][i],dp[FL][L][i-1]+Sum[S]); 这里 FL 表示上上行的状态,Sum[S] 表示当前状态 S 里面包含几个 1。
那么有了这个 dp 方程后,就可以愉快的递推了,不过这道题有几个细节需要注意一下:
1.判断每个位置是不是山丘
这个很好解决,只要把每一行的输入都转成一个二进制数(平原是 0,山丘是1),然后直接跟待判断的状态做一次位运算即可,就是 S&a[i],如果位运算结果不是零,说明有些位置放在了山丘上,也就是说当前状态不合法。
2.判断每个状态有没有两个炮兵左右距离在两格之内
这个需要动脑想一下,我们发现一个神奇的结论,如果把表示当前状态的二进制数位运算左移一位,那么用这个结果与原状态做一次位运算与操作,如果结果不是 0,那么就一定存在两个炮兵左右距离在一格之内。同理,左移两位就可以判断左右距离在两格之内。这个过程也就是 S&(S<<1),S&(S<<2)。
3.判断每一列之前两行有没有炮兵
这个就直接用当前状态分别与之前的两行即可,就是 S&L,S&FL,如果与操作结果不为零,说明有若干列前两行有炮兵,也就是说当前状态不合法。
最后说一句,一定要用滚动数组(因为只用到每一行和前两行,所以只用滚动三行),否则会 MLE 0 (我当初就是这么惨)。。。
好了细节都说完了,下面就是具体的代码实现了:
【code】
1 #include<iostream>
2 using namespace std;
3 int n,m,ans,dp[(1<<10)][(1<<10)][3]/*滚动数组*/,a[105],Sum[(1<<10)];
4 char x;
5 int getsum(int S) //当前状态 S 里面包含几个 1
6 {
7 int tot=0;
8 while(S) {if(S&1) tot++; S>>=1;}
9 return tot;
10 }
11 int main()
12 {
13 cin>>n>>m;
14 for(int i=0;i<n;i++)
15 for(int j=0;j<m;j++)
16 cin>>x,a[i]<<=1,a[i]+=(x=='H'?1:0); //转成二进制数
17 for(int i=0;i<(1<<m);i++)
18 Sum[i]=getsum(i); //初始化 Sum 数组
19 for(int S=0;S<(1<<m);S++)
20 if(!(S&a[0] || (S&(S<<1)) || (S&(S<<2))))
21 dp[0][S][0]=Sum[S]; //初始化
22 for(int L=0;L<(1<<m);L++)
23 for(int S=0;S<(1<<m);S++)
24 if(!(L&S || L&a[0] || S&a[1] || (L&(L<<1)) || (L&(L<<2)) || (S&(S<<1)) || (S&(S<<2)))) //谜之一长串特判
25 dp[L][S][1]=Sum[S]+Sum[L];
26 for(int i=2;i<n;i++)
27 for(int L=0;L<(1<<m);L++)
28 {
29 if(L&a[i-1] || (L&(L<<1)) || (L&(L<<2))) continue; //特判
30 for(int S=0;S<(1<<m);S++)
31 {
32 if(S&a[i] || L&S || (S&(S<<1)) || (S&(S<<2))) continue;
33 for(int FL=0;FL<(1<<m);FL++)
34 {
35 if(FL&L || FL&S || FL&a[i-2] || (FL&(FL<<1)) || (FL&(FL<<2))) continue;
36 dp[L][S][i%3]=max(dp[L][S][i%3],dp[FL][L][(i-1)%3]+Sum[S]); //滚动数组的实现方法
37 }
38 }
39 }
40 for(int L=0;L<(1<<m);L++)
41 for(int S=0;S<(1<<m);S++)
42 ans=max(ans,dp[L][S][(n-1)%3]); //结束状态可以是最后一行的任何状态
43 cout<<ans;
44 return 0;