题目描述
司令部的将军们打算在
N
∗
M
N*M
N∗M的网格地图上部署他们的炮兵部队。一个
N
∗
M
N*M
N∗M的地图由N行
M
M
M列组成,地图的每一格可能是山地(用
“
H
”
“H”
“H” 表示),也可能是平原(用
“
P
”
“P”
“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示
N
N
N和
M
M
M;
接下来的N行,每一行含有连续的M个字符(
‘
P
’
‘P’
‘P’或者
‘
H
’
‘H’
‘H’),中间没有空格。按顺序表示地图中每一行的数据。
N
≤
100
;
M
≤
10
N≤100;M≤10
N≤100;M≤10。
输出格式
仅一行,包含一个整数
K
K
K,表示最多能摆放的炮兵部队的数量。
输入 #1
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出 #1
6
解题思路
除了for循环打的头痛。。。(使劲点头)
我们来康康这道题
首先,这是一道状压
D
P
DP
DP,因为数据量比较状压。。。(呵呵哒,我就看不出)
分别有
N
∗
M
N*M
N∗M的地图,有缺陷的地形以及十字形的覆盖范围。
如下是 D P DP DP思考过程:
- 定义。。我们需要考虑定义,我们可以定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示到第 i i i行状态为 k k k,且上一行状态为 j j j时的最大方案数
- 初始化。。然后我们要来考虑初始化,因为状态肯定由前两行推过来,所以我们需要单独处理第一二行的方案数
- 转移方程。。取最大的话就一定要和 原来的自己、前一个状态+增长 比较,取较大的那个.
那么有了这个 d p dp dp 方程后,就可以愉快的递推了,不过这道题有几个细节需要注意一下:
- 判断每个状态有没有两个炮兵左右距离在两格之内:
这个需要动脑想一下,我们发现一个神奇的结论,如果把表示当前状态的二进制数位运算左移一位,那么用这个结果与原状态做一次位运算与操作,如果结果不是 0,那么就一定存在两个炮兵左右距离在一格之内。同理,左移两位就可以判断左右距离在两格之内。这个过程也就是 i i i& ( i < < 1 ) (i<<1) (i<<1), i i i& ( i < < 2 ) (i<<2) (i<<2)。 - 判断每一列之前两行有没有炮兵:
这个就直接用当前状态分别与之前的两行即可,就是 s [ r ] s[r] s[r]& s [ t ] , s [ t ] s[t],s[t] s[t],s[t]& s [ j ] s[j] s[j],如果与操作结果不为零,说明有若干列前两行有炮兵,也就是说当前状态不合法。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,num;
int bsy,f[102][1005][1005],s[1005],c[1005],a[103];
char ma[103];
int get(int x)//计算某一状态含有多少个1(即有多少个炮兵)
{
int e=0;
while(x>0)
{
++e;
x-=x&(-x);
}
return e;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)//读入地图,将山地(不能放兵)的地方设为1
{
scanf("%s",ma);
for(int j=0;j<m;++j)
{
if(ma[j]=='H')
a[i]+=1<<j;
}
}
for(int i=0;i<=(1<<m)-1;++i)//枚举所有的状态
{
if(((i&(i<<1))==0)&&((i&(i<<2))==0)&&((i&(i>>1))==0)&&((i&(i>>2))==0))//判断每个状态有没有两个炮兵左右距离在两格之内,即判断此状态是否正确。
{
++num;
s[num]=i;
c[num]=get(i);
if((i&a[1])==0)
f[1][0][num]=c[num];//初始化第一行
}
}
//初始化第二行
for(int i=1;i<=num;++i)//枚举第一行状态
{
for(int j=1;j<=num;++j)//枚举第二行状态
{
if(((s[i]&s[j])==0)&&((a[2]&s[j])==0))//判断是否与地形和第一行冲突
f[2][i][j]=max(f[2][i][j],f[1][0][i]+c[j]);
}
}
//dp过程
for(int i=3;i<=n;++i)//枚举当前行数
for(int j=1;j<=num;++j)//枚举当前行状态
if((a[i]&s[j])==0)//不与地形冲突
for(int r=1;r<=num;++r)//枚举前一行状态
if((s[r]&s[j])==0) //当前行状态不与前一行冲突
for(int t=1;t<=num;++t)//枚举前两行状态
if(((s[r]&s[t])==0)&&((s[t]&s[j])==0)) //不与前两行冲突,且前两行自身不冲突
f[i][r][j]=max(f[i][r][j],f[i-1][t][r]+c[j]);
for(int i=1;i<=num;++i)//枚举最后两行为结尾的情况,统计答案
for(int j=1;j<=num;++j)
bsy=max(bsy,f[n][i][j]);
printf("%d\n",bsy);
}