炮兵阵地 解题报告

 

                                                                 炮兵阵地

                                                                    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];若其为平原,有放与不放两种情况,放时前两行不能放,故:dp[i]=max(dp[i-3]+1,dp[i-1])

当列数大于1时,我们把上面的思路推广一下,定义合法的状态为当前行没有冲突的炮台,找到当前第i行合法的状态j与上一行i-1行合法的状态k,状态j的炮台数为num[j],且上一行i-1行状态k的最大炮台数通过i-2行状态l得到,则状态转移方程为:dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[j])

但是,由于炮兵攻击范围包括了三行,因此需要开三维数组,那么时间空间复杂度就是O(n*2^{2m})!显然这会爆内存和时间,因此我们需要预处理出一些合法的状态,压缩空间优化效率。

压缩数组的方法有两种:一种是预处理出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);
}

 

你可能感兴趣的:(更高更妙的动态规划,二进制与位运算)