Time Limit:1000MS Memory Limit:65536K
Description
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
文件的第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。
N≤100;M≤10。
Output
文件仅在第一行包含一个整数K,表示最多能摆放的炮兵部队的数量。
Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
Sample Output
6
Hint
状态压缩经典案例
这题我们看到的第一反应可能是求最大独立集,但这样就是一个NP难解的问题了。考虑到数据范围的特殊性(n、m较小且n>m),我们可以想到用状态压缩动态规划。
首先分析列数为1的情况:第i行的炮台数分两种情况,当其为山地时,不能放炮台,故dp[i]=dp[i-1];若其为平原,有放与不放两种情况,放时前两行不能放,故:。
当列数大于1时,我们把上面的思路推广一下,定义合法的状态为当前行没有冲突的炮台,找到当前第i行合法的状态j与上一行i-1行合法的状态k,状态j的炮台数为num[j],且上一行i-1行状态k的最大炮台数通过i-2行状态l得到,则状态转移方程为:。
但是,由于炮兵攻击范围包括了三行,因此需要开三维数组,那么时间空间复杂度就是!显然这会爆内存和时间,因此我们需要预处理出一些合法的状态,压缩空间优化效率。
压缩数组的方法有两种:一种是预处理出m列下普遍合法的状态(即不考虑山地,只考虑炮台冲突),一种是处理出每一行的合法状态(即既考虑山地,又考虑炮台冲突)。并且存状态的同时我们把该状态对应炮台数也记录下来。(判断都用位运算,详见代码和状压dp的介绍)
现在讲讲总的过程:首先进行预处理,把每一行压缩成一个二进制数,山地为1,空地为0,并用十进制存储(代码中的a数组);然后预处理出所有合法状态并存入一个数组中;接下来对于前两行分别进行dp(需要特殊处理,因为它们不用判断上两行),再对于后n-2行进行dp,最后求出最大值。
代码如下:
#include
#include
#include
using namespace std;
int n,m,tot,res; //n为行数,m为列数,tot为合法状态数,res为答案
int a[101]; //将地图每一行压缩为一个二进制数,用十进制存储
int state[65]; //存储所有合法状态
int num[65]; //存储所有合法状态对应的炮台数
int dp[101][65][65]; //dp[i][j][k]表示第i行第j种状态通过上一行第k种状态得到的最大炮台数
int get(int x) //求每种状态对应的炮台数
{
int ans=0;
for (int i=1;i<=m;i++)
if ((x&(1<<(i-1)))!=0) //判断当前位是否为1,若是则炮台数加1
ans++;
return ans;
}
void build() //预处理出所有合法状态
{
for (int i=0;i<(1<>1);
int i2=(i>>2);
if ((i1&i)==0&&(i2&i)==0&&(i1&i2)==0) //若相邻三格没有炮台冲突,则合法
{
tot++;
state[tot]=i;
num[tot]=get(i);
}
}
}
int main() //主过程
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
char c;
cin>>c;
if (c=='H') a[i]=a[i]|(1<<(j-1)); //若为山地,当前位改为1
}
build();
for (int i=1;i<=tot;i++) //对第一行进行处理,不用判断上一行
if ((state[i]&a[1])==0)
{
dp[1][i][0]=max(dp[1][i][0],num[i]);
res=max(dp[1][i][0],res);
}
for (int i=1;i<=tot;i++) //对第二行进行处理,只用判断上一行
if ((state[i]&a[2])==0)
for (int j=1;j<=tot;j++)
if ((state[j]&a[1])==0)
{
dp[2][i][j]=max(dp[2][i][j],dp[1][j][0]+num[i]);
res=max(dp[2][i][j],res);
}
for (int now=3;now<=n;now++) //对后n-2行进行处理,要判断前一行和前两行是否合法或冲突
for (int i=1;i<=tot;i++)
if ((state[i]&a[now])==0)
for (int j=1;j<=tot;j++)
if ((state[j]&a[now-1])==0)
for (int k=1;k<=tot;k++)
if ((state[k]&a[now-2])==0)
{
if ((state[i]&state[j])!=0||(state[j]&state[k])!=0||(state[i]&state[k])!=0) continue; //判断上一行和上上行同列有无冲突炮台
dp[now][i][j]=max(dp[now][i][j],dp[now-1][j][k]+num[i]);
res=max(dp[now][i][j],res);
}
printf("%d",res);
}