Poj 1185/Hrbust 1844 炮兵阵地【状压dp】


炮兵阵地
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 25502 Accepted: 9836

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

Source


思路:

如果小伙伴们对状压dp还不是很了解的话,大家可以先考虑考虑做做这个题并参考参考我的详解:http://blog.csdn.net/mengxiang000000/article/details/51075506


1、设定dp【i】【j】【k】表示现在dp过程到了第i行,第i行状态为j第i-1行状态为k的最大放置炮台的个数。对于状态来讲,我们使用十进制数来保存,其含义为:其转化成2进制之后,对应位数字为1表示这个位子上放置炮台,数字为0表示这个位子上不放置炮台。


2、那么不难理解:

dp【i】【j】【k】=max(dp【i】【j】【k】,dp【i-1】【k】【l】+第i行状态j放置的炮台个数);

虽然这个状态转移方程式一点问题都没有,但是呢,它的设定会超内存。所以呢,我们再次设定:

dp【i】【j】【k】表示现在dp过程到了第i行,第i行的可行状态编号为j,第k行的可行状态编号为k的最大放置炮台的个数

e.g:某一行:PHHP

可行方案有三种:

8

9

对应我们给其编号为;

1  1

2 8

3 9

因为可行方案并不会太多,所以我们可以更多的释放内存。


3、那么我们在输入完图之后,初始化搞定这个可行状态的保存问题:

①设定tmp=这一行H的情况,e.g:PHPP,tmp=2;(我们设定最左边的是第一位)

②枚举这一行所有可能的状态j【(0<=j<(1<<m)】,然后判断这种状态是否合法,首先,j&tmp==0,表示在H的地方,我们没有放置不合法的炮台。

③然后我们将这种状态保存入数组a【i】【contz】,并且记录当前状态中有多少个1(就是有多少个炮台),记录在add【i】【contz】中。

对应代码:

int judge(int j,int k,int l)
{
    if((j&k)!=0)return 0;
    if((j&l)!=0)return 0;
    if((k&l)!=0)return 0;
    return 1;
}
 int end=(1<<m);
        for(int i=0;i<n;i++)
        {
            int tmp=0;
            int contz=0;
            for(int j=0;j<m;j++)
            {
                if(aa[i][j]=='H')
                {
                    tmp+=(1<<j);
                }
            }
            for(int j=0;j<end;j++)
            {
                if(judgerow(j)==1)
                {
                    if((j&tmp)==0)
                    {
                        int sum=0;
                        int tmpp=j;
                        while(tmpp)
                        {
                            if((tmpp&1)!=0)sum++;
                            tmpp/=2;
                        }
                        a[i][contz]=j;
                        add[i][contz++]=sum;
                    }
                }
            }
            kk[i]=contz;
        }




4、然后我们进行状态转移,我们从第二行开始搞定这个问题(如果输入只有一行,我们单独处理)

①枚举第i行的状态j和i-1行的状态k,得到dp【i】【j】【k】的三个元素i,j,k。

②然后枚举一个状态l,得到dp【i-1】【k】【l】的三个元素i-1,k,l。

③然后判断一下j,k,l三个状态有没有上下矛盾的情况,如果没有,维护dp【i】【j】【k】的值。

对应代码:


int judgerow(int j)
{
    if((j&(j<<1))!=0)return 0;
    if((j&(j<<2))!=0)return 0;
    if((j&(j>>1))!=0)return 0;
    if((j&(j>>2))!=0)return 0;
    return 1;
}

for(int i=1;i<n;i++)
        {
            for(int jj=0;jj<kk[i];jj++)
            {
                for(int ll=0;ll<kk[i-1];ll++)
                {
                    int j=jj;
                    int k=ll;
                    int addd=add[i][jj];
                    if(i==1)
                    {
                        addd+=add[i-1][ll];
                        if(judge(a[i][j],a[i-1][k],0)==1)
                        {
                            dp[i][j][k]=max(dp[i][j][k],addd);
                        }
                        continue;
                    }
                    for(int pp=0;pp<kk[i-2];pp++)
                    {
                        int l=pp;
                        if(judge(a[i][j],a[i-1][k],a[i-2][l])==1)
                        {
                            dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+addd);
                        }
                    }
                }
            }
        }


5、整个思路和核心代码搞定了之后:

ans=max(dp【n-1】【i】【j】);

维护一下即可。


完整Ac代码:


#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
//int dp[150][1024][1024];
int dp[150][120][120];
int kk[150];
int add[150][1050];
int a[150][1050];
char aa[150][20];
int n,m;
int judge(int j,int k,int l)
{
    if((j&k)!=0)return 0;
    if((j&l)!=0)return 0;
    if((k&l)!=0)return 0;
    return 1;
}
int judgerow(int j)
{
    if((j&(j<<1))!=0)return 0;
    if((j&(j<<2))!=0)return 0;
    if((j&(j>>1))!=0)return 0;
    if((j&(j>>2))!=0)return 0;
    return 1;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++)
        {
            scanf("%s",aa[i]);
        }
        int end=(1<<m);
        for(int i=0;i<n;i++)
        {
            int tmp=0;
            int contz=0;
            for(int j=0;j<m;j++)
            {
                if(aa[i][j]=='H')
                {
                    tmp+=(1<<j);
                }
            }
            for(int j=0;j<end;j++)
            {
                if(judgerow(j)==1)
                {
                    if((j&tmp)==0)
                    {
                        int sum=0;
                        int tmpp=j;
                        while(tmpp)
                        {
                            if((tmpp&1)!=0)sum++;
                            tmpp/=2;
                        }
                        a[i][contz]=j;
                        add[i][contz++]=sum;
                    }
                }
            }
            kk[i]=contz;
        }
        for(int i=1;i<n;i++)
        {
            for(int jj=0;jj<kk[i];jj++)
            {
                for(int ll=0;ll<kk[i-1];ll++)
                {
                    int j=jj;
                    int k=ll;
                    int addd=add[i][jj];
                    if(i==1)
                    {
                        addd+=add[i-1][ll];
                        if(judge(a[i][j],a[i-1][k],0)==1)
                        {
                            dp[i][j][k]=max(dp[i][j][k],addd);
                        }
                        continue;
                    }
                    for(int pp=0;pp<kk[i-2];pp++)
                    {
                        int l=pp;
                        if(judge(a[i][j],a[i-1][k],a[i-2][l])==1)
                        {
                            dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+addd);
                        }
                    }
                }
            }
        }
        if(n==1)
        {
            int ans=0;
            for(int i=0;i<kk[0];i++)
            {
                ans=max(add[0][i],ans);
            }
            printf("%d\n",ans);
            continue;
        }
        int ans=0;
        for(int i=0;i<kk[n-1];i++)
        {
            for(int j=0;j<kk[n-2];j++)
            {
                ans=max(dp[n-1][i][j],ans);
            }
        }
        printf("%d\n",ans);
    }
}






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值