炮兵部队 状态压缩动态规划的模板题

Description
司令部的将军们打算在N×M的网格地图上部署他们的炮兵部队。
一个N×M的地图由N行M列组成,地图的每一格可能是山地(用”H”表示),也可能是平原(用”P”表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队)。
一支炮兵部队在地图上的攻击范围如图中黑色区域所示:灰色区域为该炮兵部队位置,黑色区域为炮兵可攻击范围
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入输出格式
输入格式:
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(’P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。N≤100,M≤10。
输出格式:
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

观察题目,我们可以发现,对于第i行,第j列的位置p[i][j],它是否有炮兵取决于此地地形,p[i-1][j],p[i-2][j],p[i][j-1],p[i][j-2] 这四个地点是否有炮兵。同时,它会对 p[i][j+1],p[i][j+2],p[i+1][j],p[i+2][j] 造成影响。
但是,我们却很难得到一个准确的状态转移方程,因为,我们如果使用f[i][j]记录第i行,第j列的地点有一个炮兵,而在此之前的所有炮兵的和最大为多少,我们需要从f[i-1][M]转移过来,但是,我们并不能确定f[i-1][M] 中,是否含有一个炮兵在p[i-1][j],因此,我们需要使用状态压缩的方式,将每行M个位置的状态压缩到一个数字中,这样,我们就可以一行一行的状态转移,每次转移只需要判断该行的所有炮兵是否在对应的列上,上一行,以及上一行的上一行是否有同样对应的列的炮兵,就可以判断是否可以转移了。
那么,我们该怎么样状态压缩呢?
首先,我们可以把存在炮兵看作是1,不存在炮兵看作是0,那么,每一行的状态我们可以看作是连起来的二进制串,由于M≤10,2^10=1024,所以,我们完全可以建出这个大小的数组然后进行转移。
所以,我们可以建出数组 f[i][j],存储第i行处于第j个状态的情况下,所能得到的最大值。
但是,我们会发现,转移状态的过程中,我们需要保证状态j与(i-1)的状态、(i-2)的状态均不重合,所以,我们可以将两行的不同状态组合看作一个状态。
这样,我们就需要使用f[i][j][k]来存储第i行处于状态j,并且i-1行处于状态k时,前i行的最多炮兵。
那么,我们此时得到第i行的转移方程为
f[i][j][k]=max(f[i][j][k],f[i-1][k][p]+sum[j]) (1≤i≤n,j,k,p 状态每一列都没有两个及以上的炮兵 )
当然,如果企图使用 f[i][j]来记录当前第i行,状态为j时,前i行的最大炮兵数量,则会出现一些无法解决的问题。
因为如果这样做,则需要保证 (j,k,p 状态每一列都没有两个及以上的炮兵),而此时转移过程中,使用f[i][j]=f[i-2][p]+sum[j]+sum[k],而我们无法保证f[i-2][p]中的i-3行与我们得到的i-1行k状态是否存在冲突,如果要保证它不冲突,则要继续递归下去,所以这种方法并不可行。
所以,我们采取这种两行状态作为一个状态来转移的方式,就可以很快得到答案了。
代码如下:

#include <bits/stdc++.h>

using namespace std;

int n,m,tot,ans;
int x[101];
int f[101][61][61];
int v[1025],num[1025];

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            char c=getchar();
            while ((c!='P')&&(c!='H'))
                c=getchar();
            x[i]<<=1;
            if (c=='H')
                x[i]++;
        }
    for (int i=0;i<1<<m;i++)
        if ((!(i&(i<<1)))&&(!(i&(i<<2))))
        {
            v[++tot]=i;
            int a=i;
            while (a)
            {
                if (a&1)   
                    num[tot]++;
                a>>=1;
            }
        }
    for (int j=1;j<=tot;j++)
        if (!(v[j]&x[1]))
            f[1][j][1]=num[j];
    for (int j=1;j<=tot;j++)
        if (!(v[j]&x[2]))
            for (int k=1;k<=tot;k++)
                if ((!(v[j]&v[k]))&&(!(v[k]&x[1])))
                    f[2][j][k]=f[1][k][1]+num[j];
    for (int i=3;i<=n;i++)
        for (int j=1;j<=tot;j++)
            if (!(v[j]&x[i]))
            {
                for (int k=1;k<=tot;k++)
                    if ((!(v[j]&v[k]))&&(!(v[k]&x[i-1])))
                    {
                        int maxn=0;
                        for (int p=1;p<=tot;p++)
                            if ((!(v[k]&v[p]))&&(!(v[p]&x[i-2]))&&(!(v[j]&v[p])))
                                maxn=max(maxn,f[i-1][k][p]);
                        f[i][j][k]=maxn+num[j];
                        if (i==n)
                            ans=max(ans,f[i][j][k]);
                    }
            }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值