炮兵阵地(DP状态压缩)

司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由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

这道题首先使用DP做,需要注意的是,因为每一行的状态更新都是需要知道前面两行的排列的。如果只用dp[n][1<<m]来储存第i行排列为j的时候的最大数量是不行的。因为要调用三成循环去枚举前两行的排列和当前行的排列,而我们的状态转移方程只能写成dp[i][j]=max(dp[i][j],dp[i-1][l]+number[i]).这样的方程保证不了dp[i-1][l]取得最大值时上一层的值,一定是当前行的上上一层的排列。所以应该至少保存两层的信息。dp[i][j][k]:表示第i行为j排列第i-1行为k排列的最大值。我是先分别更新了前两行的dp值(如果行数大于等于2的话),然后再去枚举第i行。

注意。枚举三行的时候一定要从当前行到前面两行依次枚举走。这样才能用到前面已经更新过的值
代码如下。

#include <iostream>
#include <string.h>
int n,m;
using namespace std;
int fit(int a,int b)
{
    return (a|b)==b;
}
int no_fight(int pre,int now)
{
    if( (pre&now) ==0)
        return 1;
    return 0;
}
int pick_number(int a)
{
    int number=0;
    for(int i=0; i<m; i++)
    {
        if((a&1) ==1)
            number++;
        a=(a>>1);
    }
    return number;
}
int main()
{
    cin>>n>>m;
    int dp[n][1<<m][1<<m];//必须保存两行的信息才可以 dp[i][j][k]i表示第i行j表示第i行的储存信息,j表示第i-1行的储存信息
    for(int i=0; i<n; i++)
        for(int j=0; j<(1<<m); j++)
            for(int k=0; k<(1<<m); k++)
                dp[i][j][k]=0;
    int Map[n];
    memset(Map,0,sizeof(Map));
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<m; j++)
        {
            char ch;
            cin>>ch;
            if(ch=='P') Map[i]=Map[i]|(1<<(m-j-1));
        }
    }
    int state[1<<m];
    int k=0;
    int max_first=0,max_seconde=0;
    int ans=0;
    for(int i=0; i<(1<<m); i++)
    {
        if( ((i>>1)&i)==0 && ((i>>2)&i)==0 )
        {
            state[k++]=i;
            if(fit(i,Map[0])==1)
            {
                for(int j=0; j<(1<<m); j++)
                {
                    dp[0][i][j]=pick_number(i);
                }
                max_first=max(max_first,dp[0][i][0]);
            }
        }
    }//每一行不会攻击到自己炮兵的可行数 并且同时更新了第一行的dp值

    //再枚举第二行的dp值
    if(n>1)
    {
        for(int i=0; i<k; i++)
        {
            int row1=state[i];
            if(!fit(row1,Map[0]))
                continue;
            for(int j=0; j<k; j++)
            {
                int row2=state[j];
                if(!fit(row2,Map[1])|| !no_fight(row1,row2))
                    continue;
                dp[1][row2][row1]=pick_number(row1)+pick_number(row2);
                max_seconde=max(max_seconde,dp[1][row2][row1]);
            }
        }
    }
    else
    {
        ans=max_first;
        cout<<ans;
        return 0;
    }

    //再枚举第i行的所有情况
    if(n>2)
    {
    for(int i=2; i<n; i++)
    {
        for(int x3=0; x3<k; x3++)
        {
            int row3=state[x3];
            if(!fit(row3,Map[i]))
                continue;
            for(int x2=0; x2<k; x2++)
            {
                int row2=state[x2];
                if(!fit(row2,Map[i-1])|| !no_fight(row2,row3))
                    continue;
                for(int x1=0; x1<k; x1++)
                {
                    int row1=state[x1];
                    if(!fit(row1,Map[i-2]) || !no_fight(row1,row3) || !no_fight(row1,row2))
                        continue;
                    dp[i][row3][row2]=max(dp[i][row3][row2],dp[i-1][row2][row1]+pick_number(row3));
                    if(i==n-1)
                    {
                        ans=max(ans,dp[i][row3][row2]);
                    }
                }
            }
        }
    }
    }
    else
    {
        ans=max_seconde;
        cout<<ans<<endl;
        return 0;
    }
    cout<<ans;
    return 0;
}

                                                          ——— 自己选的路,跪着也要走完。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值