炮兵阵地

炮兵阵地

题目描述

在这里插入图片描述


题意解释

条件:

  • 山地上不能部署炮兵部队
  • 沿横向左右两格,沿纵向上下两格

题目问“最多”,是求最大值。、

我们用1来表示平原,用0来表示山地。

在这里插入图片描述


核心思路

在这里插入图片描述

如何理解下面这段代码呢?

for(int i=1;i<=n;i++)
{
    for(int j=0;j<m;j++)
    {
        char c;
        cin >>c;
        if(c=='P')
            g[i]+=1<<(m-j-1);		//保存地图各行的数值
    }
}

如下图所示

第一行输入PHHP,即1001,换算成十进制数就是9,所以g[1]=9。

第二行输入HPHP,即0101,换算成十进制数就是5,所以g[2]=5

第三行输入HHPH,即0010,换算成十进制数就是2,所以g[3]=2

在这里插入图片描述

如何理解s数组和num数组呢?

在这里插入图片描述

在这里插入图片描述

使用滚动数组优化:

int f[N][M][M];

如果这么写就会爆炸空间了, 110 ∗ 1024 ∗ 1024 ∗ 4 = 440 M B > 64 M B 110*1024*1024*4=440MB>64MB 110102410244=440MB>64MB,我们从更新代码发现,第i行的状态只与第i-1行的状态有关,所以第一维只开2个空间就行了。

2 ∗ 1024 ∗ 1024 ∗ 4 = 8 M B < 64 M B 2*1024*1024*4=8MB<64MB 2102410244=8MB<64MB,因此可以使用滚动数组来优化,这样就不会MLE了。

下图左边是没有使用滚动数组,右边是使用滚动数组。

在这里插入图片描述

为什么还需要考虑(s[b]&g[i-1])==s[b]呢?

因为我们是先固定好第i行和第i-1行,然后再去判断第i-2行。因此,必须先让第i行和第i-1行满足两个限制条件才可以。因此还要加上 ( s [ b ] & g [ i − 1 ] ) = = s [ b ] (s[b]\&g[i-1])==s[b] (s[b]&g[i1])==s[b]

为什么是1<<(m-j-1)呢?

因为比如输入PHPP,二进制就是1011。那么从前往后输入其实就是从高位到低位,但是我们枚举的j是从低位到高位,因此使用m-j-1就可以表示从高位到低位了,这样相加时,就不会出错了,至于为什么减去1,是因为j从0开始枚举,如果是从1开始枚举,则可以写成m-j的形式。

for(int i=1;i<=n;i++)
{
    for(int j=0;j<m;j++)
    {
        char c;
        cin >>c;
        if(c=='P')
            g[i]+=1<<(m-j-1);
    }
}

//或者这么写
for(int i=1;i<=n;i++)
{
    for(int j=1;j<=m;j++)
    {
        char c;
        cin >>c;
        if(c=='P')
            g[i]+=1<<(m-j);
    }
}

对于合法状态要与地形适应的解法:

一般都是用 ( s [ a ] & g [ i ] ) = = s [ a ] (s[a]\&g[i])==s[a] (s[a]&g[i])==s[a]来判断,如果为真,则说明合法状态与地形是相适应的,否则就不是。但是使用这个公式的前提是,我们要明确【合法状态】中’1’的含义和【地形】中’1’的含义。


代码

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=110,M=1<<10;
int n,m;//行数、列数
//比如0000,0001,0010,0100,1000,1001就是合法状态,那么cnt=6
int cnt;//同一行的所有合法状态的个数
int g[N];   //g[i]表示第i行的状况,保存地图各行数值,比如第二行输入PPHH,则为1100,所以g[2]=12
int s[M];
//int s[M];//存储合法状态的集合,比如s={0000,0001,0010,0100,1000,1001}
//比如对于0000,那么num[0]=0;对于0001,num[1]=1;对于0010,num[2]=1;对于0100,num[3]=1;对于1000,num[4]=1
//对于1001,num[5]=2
int num[M];//统计每个合法状态中含有1的个数 
//如果这么写就会爆炸空间了,110*1024*1024*4=440MB>64MB
//int f[N][M][M];
//使用滚动数组优化 我们从更新代码发现,第i行的状态只与第i-1行的状态有关,所以第一维只开2个空间就行了
//2*1024*1024*4=8MB<64MB
//f[i][a][b]表示已经放好了前i行,第i行第a个状态,第i-1行第b个状态时,能放置的最大数量
int f[2][M][M];
int main()
{
    scanf("%d%d",&n,&m);//输入行数和列数
    //预处理地图
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<m;j++)
        {
            char c;
            cin >>c;
            if(c=='P')//把平原P看作是1,把山地H看作是0
            g[i]+=1<<(m-j-1);   //保存地图各行数值
        }
    }
    
    //预处理每行的所有合法状态,并统计某个合法状态中有多少个1
    for(int i=0;i<(1<<m);i++)
    {
        if(!(i&i>>1)&&!(i&i>>2))//如果不存在11或者101之类的
        {
            s[cnt++]=i;//保存一行的合法状态
            for(int j=0;j<m;j++)
            num[i]+=(i>>j&1);   //统计每个合法状态包含1的个数
        }
    }
    f[0][0][0]=0;
    //从第1行枚举到第n+2行
    for(int i=1;i<=n+2;i++)
    {
        for(int a=0;a<cnt;a++)//枚举第i行的合法状态
        {
            for(int b=0;b<cnt;b++)//枚举第i-1行的合法状态
            {
                for(int c=0;c<cnt;c++)//枚举第i-2行的合法状态
                {
                    if(!(s[a]&s[b])&&!(s[a]&s[c])&&!(s[b]&s[c])&&(s[a]&g[i])==s[a]&&(s[b]&g[i-1])==s[b])
                    {
                        //由于f数组的第一维只有两个空间,因此第一维空间下标只能是0和1,不能是2
                        //那么我们可以对2取模,这样就可以保证得到的结果是小于2的了,即0和1
                        //i&1就相当于i%2                 减法优先级高于与运算
                        f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][b][c]+num[s[a]]);
                    }
                }
            }
        }
    }
    cout <<f[n+2&1][0][0]<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值