F - 炮兵阵地

F - 炮兵阵地
Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u

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

这个状态压缩题感觉还是很有难度的,对于我们这种初学区间dp的人来说。通过这道题学长交给我们很多小知识点。用二进制数来存每个状态(1表示放一个炮台,0表示不放),一行有10个数,也就是说有2的10次方(1024)个状态,如果不提前处理那些限制条件,直接开一个dp[105][1050][1050]会超内存的,之前我就是这样超了,根据题意要先去掉存在相邻的1(11)和隔一个同为1(101)这样的状态,然后就会发现只剩下60种可能有效状态,因此开个dp[105][65][65]就足够了。

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[106][65][65];
int mm,sta[65],num[65],b[105];
void init(int m)
{
        for(int i=0;i<(1<<m);i++)
        {
            int x1=i<<1;                    //左移一位再与本身按位与可以去掉相邻的1这种状态
            int x2=i<<2;                    //左移两位再与本身按位与可以去掉隔一个同为1的这种状态   
            if((x1&i)==0&&(x2&i)==0)
                sta[mm++]=i;                //记录下每一个有效状态的二进制值
        }
        memset(num,0,sizeof(num));
        memset(b,0,sizeof(b));
        for(int t=1;t<mm;t++)
            for(int i=0;i<m;i++)
        {
            int x3=1<<i;
            if((x3&sta[t])!=0)
                num[t]+=1;              //对应的记录下每个有效状态中有多少个1
        }
}
int main()
{
    char a[105][20];
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        mm=0;
        init(m);
        for(int i=0;i<n;i++)
        {
            scanf("%s",a[i]);
            for(int j=0;j<m;j++)
                if(a[i][j]=='H')
                    b[i]+=1<<j;                //如果为H是不能放炮台的,所以记录下每一行的那些位置不能为1
        }
        int ans=0;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<mm;i++)                  //预处理1,2行的dp值
            for(int j=0;j<mm;j++)
        {
            if(!(b[0]&sta[i]))
            {
                dp[0][i][0]=num[i];
                ans=max(ans,dp[0][i][0]);
            }
            if(!(b[1]&sta[j])&&!(sta[i]&sta[j])&&n>=2)          //加上n>2这个条件是因为当n为1时输出的值不能被这个地方改变
            {
                dp[1][j][i]=max(dp[1][j][i],num[j]+dp[0][i][0]);
                ans=max(ans,dp[1][j][i]);
            }
        }
        if(n<=2)
        {
            printf("%d\n",ans);
            continue;
        }
        for(int i=2;i<n;i++)
        for(int l=0;l<mm;l++)
        {
            for(int k=0;k<mm;k++)
        {
            for(int j=0;j<mm;j++)
            {
                if((b[i]&sta[j])==0&&!(sta[j]&sta[l])&&!(sta[j]&sta[k])&&!(sta[l]&sta[k]))//因为一个炮台的攻击范围是上下左右各两格,所以这里要判断当前行与上一行还有上上行三行之间不能存在相同位置为1
                {
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[j]);
                    ans=max(ans,dp[i][j][k]);
                }
            }
        }
        }
        printf("%d\n",ans);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值