Dp-状态压缩:炮兵阵地详解版

在这里插入图片描述

思路分析:

这种棋盘类型的问题+数据范围。看起来就巨像状态压缩。

玉米田那道题特别想,只不过那道题只攻击相邻的,这里的话攻击范围加大了一个格子。

状态表示

我们以不同的行 为不同的状态进行划分。由于这里第i行的摆放状态会受到i-1行和i-2行的同时的影响。因此,之前的状态表示f[i][j]不够用了。现在需要开到三维。不对,而应该理解成第i-2行的状态同时会影响到i-1行和i行。 枚举合法的第 i−2i−2 层状态进行转移

  • 在玉米田中:
    在这里插入图片描述
  • 在炮兵阵地中:
    在这里插入图片描述

状态转移

  • 转移的合法性限制
    相邻距离小于等于二的两列,不能互相攻击到。
    在这里插入图片描述
  • 自身的合法性限制
    在同一行内,大炮不能互相攻击到。
    如果位置i有大炮,那么i+1和i+2的位置必须为0,两个同时为0 为true的话需要用||操作。
    在这里插入图片描述
    大炮不能摆放在山上。
    在这里插入图片描述

状态初始化:

f[0][0][0] = 1

目标状态:

在这里插入图片描述
需要枚举第n行,所有合法的状态j,k。然后取一个最大值

和前面一样,f[n+2][0][0]表示n+2行的状态为0,n+1行状态为0的摆放方案的最大值。此时证明n行已经摆好了。就是我们想要的答案。

好几张图片取自:https://www.acwing.com/solution/content/57859/

优化:在这里插入图片描述



#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;

const int N = 110,M = 1 << 10;
int f[2][M][M]; // 第一维使用了滚动数组
vector<int>state; 
vector<int>pre[M];
int n,m;
int g[N]; // g[i]存第i行的地图
int cnt[M]; // cnt[i] 记录i中1的个数

bool check(int state)
{
    for(int i = 0 ; i < m ; i ++)
        if((state >> i & 1)&&((state >> i + 1 & 1)||(state >> i + 2 & 1)))return false;

    return true;    
}

int count(int state)
{
    int res=0;
    for(int i = 0 ; i < m ; i ++)res += state >> i & 1;
    return res;
}

int main()
{
    cin>>n>>m;

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

    for(int i = 0 ; i < 1 << m ; i ++)
        if(check(i))
        {
            state.push_back(i);
            cnt[i] = count(i); 
        }

    //预处理合法状态能转移的状态    
    for (auto state_st : state)
        for (auto pre_st : state)
        	// 如果对应位没有同时为1
            if (!(state_st & pre_st))
                pre[state_st].push_back(pre_st);
                
    // f[i][j][k] :考虑前 i 层,且第 i 层状态是 j,第 i−1 层状态是 k 的方案
    // 枚举每一层
    for(int i = 1 ; i <= n + 2 ; i ++)
        for (auto i_st : state)//枚举当前层 
            if (!(g[i] & i_st))//第i层合法
                for (auto j_st : pre[i_st])//枚举上一层
                    if (!(g[i - 1] & j_st))//第i - 1层合法
                        for (auto u_st : pre[j_st])//枚举上二层
                            if (!(i_st & u_st))//判断当前行与前两行是否冲突
                                // 每次求一个最大值
                                f[i & 1][j_st][i_st] = max(f[i & 1][j_st][i_st] ,
                                                            f[i - 1 & 1][u_st][j_st] + cnt[i_st]);

    cout<<f[n + 2 & 1][0][0]<<endl;

    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值