POJ 1185 炮兵阵地(状态压缩DP)

司令部的将军们打算在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

首先一看 , 搜索做啊,Em………会超
网上都是用状态压缩DP做的

先说下 状态压缩吧
状态压缩:
解法需要保存一定的状态数据(表示一种状态的一个数据值),每个状态数据通常情况下是可以通 过2进制来表示的。

  1. X<<1 X>>5
  2. p|( 1<< k) 将p的第k+1位变成1
  3. p&(1<< k) 判断p的第k+1位是否为1
  4. a & b eg: 10000 & 01111 = 00000
  5. x&(x-1) 把二进制最靠右的第一个1去掉

上面5种状态压缩常用的操作 本题用的是1, 2, 4
注意理解含义

题目分析:

对于第k行哪些点放炮兵使得1~k行炮兵数最多与第k-1,k-2行哪些点放炮兵有关,所以对于第k行需要前面两行的(k-1,k-2)的状态才能确定。
所以对于第k行需要前面两行的(k-1,k-2)的状态才能确定,最暴力的方法莫过于dp[k-1][][][]…[k-2][][][],表示第k-1行第1,2,3..个位置放与不放炮兵并且第k-2个位置放与不放炮兵时来确定第k行的dp.

我们先理一下思路:

既然 本行数据和前2行有关, 那么我们首先需要计算每行的情况和情况数, 那么我们在计算过程就用到了 二进制操作来进行 状态压缩

我看看一下测试样例前3行
PHPP
PPHH
PPPP
P代表平地,那么炮兵为可放(1) 或者 可不放(0)的状态, N代表高原,那么一定不能放(0)
那么初始3行 假设能放的全放 用2进制表示为
1011
1100
1111

但是为了使代码好写 我们使用它的逆编码(先不要管,后面 说,方便操作而已)
1101
0011
1111

那么 我们来计算每行所能达到的情况数
第一行 1101
他有 1001 0001 0100 1000 四种情况
第二行 0011
有0001 0010 两种情况
第三行1111
有1001 0001 0010 0100 1000 5种情况
到这 还能看懂吧
等一下 其他他们还有一种情况就是 都不放的情况 0000

那么我们代码中dfs的操作就是执行状态压缩过程的 转化为二进制编码

if(Map[id][k] == ‘P’) //找到可安放炮兵的位置
dfs(id,k+3,p|(1<< k),sum+1); //避免互相伤害, 从后第三位在开始

找到可放位置时 将每行的每种状态存入now数组 将每种状态的sum值存入num数组 , sum值代表当前状态能放的个数

然后就是dp操作 通过上上次 和上次的状态 等到当前行的状态

code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <map>
#include <cmath>
#include <iomanip>
#define INF 99999999
typedef long long LL;
using namespace std;

const int MAX=100+10;
int n,m,lastsize,lastlastsize,nowsize;  //nowsize当前行的方案数,lastsize上一行的方案数,                                           lastlastsize的上上行方案数
int last[MAX],lastlast[MAX],now[MAX];  // now[i]第i行的方案......
int num[MAX],dp[MAX][MAX],temp[MAX][MAX];  //dp[i][j]  i代表当前行 j代表上一行
//temp[j][t] j上一行 t上上行
char Map[MAX][MAX];

void dfs(int id,int k,int p,int sum)
{
    if(k>=m)
    {
        now[++nowsize]=p;
        printf ("%d\n",p);
        num[nowsize]=sum;
        return;
    }
    if(Map[id][k] == 'P')                   //找到可安放炮兵的位置
        dfs(id,k+3,p|(1<<k),sum+1);
    dfs(id,k+1,p,sum);
}

void DP()
{
    for(int k=1; k<=n; ++k)
    {
        memset(now,0,sizeof now);
        nowsize=0;
        dfs(k,0,0,0);
        for(int i=1; i<=nowsize; ++i)
            for(int j=1; j<=lastsize; ++j)
                dp[i][j]=0;
        for(int i=1; i<=nowsize; ++i) //本行选择第几个方案
        {
            for(int j=1; j<=lastsize; ++j) //上一行选择第几个方案
            {
                for(int t=1; t<=lastlastsize; ++t) //上上行选择第几个方案
                {
                    if(now[i] & last[j])
                        continue;//与上一行j方案不能共存
                    if(now[i] & lastlast[t])
                        continue;//与上上行t方案不能共存
                    if(dp[i][j]<temp[j][t]+num[i])
                        dp[i][j]=temp[j][t]+num[i];
                }
            }
        }
        printf ("%d %d %d\n",nowsize,lastsize,lastlastsize);
        for(int i=1; i<=nowsize; ++i)     //将当前状态赋给上一次的, 进行下次操作
            for(int j=1; j<=lastsize; ++j)
                temp[i][j]=dp[i][j];

        for(int i=1; i<=lastsize; ++i)
            lastlast[i]=last[i];
        lastlastsize=lastsize;

        for(int i=1; i<=nowsize; ++i)
            last[i]=now[i];
        lastsize=nowsize;
    }
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<=n; ++i)scanf("%s",Map[i]);
        last[1]=lastlast[1]=temp[1][1]=0;
        lastsize=lastlastsize=1;
        DP();
        int sum=0;
        for(int i=1; i<=lastsize; ++i)    //便利dp值 找到其中最大的sum值
        {
            for(int j=1; j<=lastlastsize; ++j)
            {
                if(temp[i][j]>sum)
                    sum=temp[i][j];
            }
        }
        printf("%d\n",sum);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值