AcWing 292. 炮兵阵地(经典棋盘式状压dp加强版)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本题是前两道题:AcWing 1064. 小国王AcWing 327. 玉米田 的增强版。

题意:

我们打算在一张 N × MNM列) 的棋盘上摆放棋子,当棋盘某一格的字符为 H,表示山地(我们用 1 表示),字符为 P,表示平原(我们用 0 表示),其中 只有平原才能摆放棋子,当摆放好某个棋子后,其攻击范围是 2,“横向左右各两格,纵向上下各两格” 都不能再摆放棋子。

问:这个棋盘上 最多能摆放多少棋子

思路:

设定:从 前行 往 后行 摆放棋子,

我们考虑一下,对于第 i 行的摆放方案会受 哪些影响

首先,需要第 i 行中任意两相邻的 1 之间距离大于等于 2,换句话说,如果第 i 行中某一位为 1,那么其后 至少两个位置 必须同时为 0

对此我们编写一个check函数(一个合法的状态state至少要满足这个条件)

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

其次,根据题意,显然它会受到第 i-1 行 和 第 i-2 行的影响,因为放在第 i-2 行的炮兵可以打到第 i

结论:当考虑第 i 行状态时,我们 只需关注其前两行状态

状态表示:

集合f[i, j, k]表示已经摆完前 i 行,且所有摆放的炮兵之间不能相互攻击到,并且每个炮兵都不在山地上,并且第 i 行的摆放状态是 k,第 i-1 行的摆放状态是 j所有方案的集合

属性:最多摆放的棋子Count

状态计算:

集合划分:由于第 i 行、第 i-1 行的摆放状态 kj 已经确定,本着 “以最后一步不同点划分” 的原则,我们可以以第 i-2 行的摆放状态划分。

现在我们需要枚举第 i-2 行的状态 u ,对于第 i-2 行每一种状态都需要 计算最多摆放的棋子数量,最终总数量f[i, j, k]所有类别的子集取 max

接下来考虑一下分得的 每一子类 最多摆放的棋子数量 如何计算:

其中某一类 表示:第 i-2 行状态为 u

我们应当从 两方面 进行考虑:

  • 状态 u 是否是合法状态
  • 最多摆放的棋子数量是多少

在代码中我们用 a、b、c 来分别表示 i-2、i-1、i 三行状态,对于上述 ①,需要判断两个条件:

  • (1)ab、ac、bc是否有交集 即:if((a&b) || (a&c) || (b&c))
  • (2)第 i 行状态 是否有将棋子摆放到不合法的山地上第i-1、i-2两行状态已经能保证合法)即:if(c&g[i])

当以上两个“if”有一个为真,即 第 i-2 行状态 u不合法 状态,continue

对于上述 ②,由于前 i-1最多摆放的棋子数f[i-1, u, j],所以前 i 行最多摆放的炮兵数量 等于 在前i-1行基础上加上 第 i 行炮兵数量(即满足 “同一行内不能相互攻击,即相邻棋子距离大于等于 2” 的状态 c1 的个数,可见需要有一个 计算二进制数中 1 的位数的函数

最终可以得到 状态转移方程式,代码体现为两者取maxdp[i][b][c] = max(dp[i][b][c], dp[i-1][a][b] + cnt[c]);

时间复杂度:

行数(100)× 第二维(2 ^ 10)× 第三维(2 ^ 10)× 转移数量(2 ^ 10)≈ 100 × 1000 ^ 3 = 1e11

有了之前的经验,我们可以推断每一行内部的合法状态(相邻两个 1 之间需要包含至少两个 0)其实很少,总共大概只有 100 左右,所以可以在规定时间内算出。

空间复杂度:

对于本题来说,直接开所有的状态空间,空间复杂度是 O(N × 2 ^ M × 2 ^ M) 是会爆内存的(题设为64MB)

因此我们必须使用 滚动数组 进行优化,具体应对见代码

代码:

关于 预处理合法状态目标状态输出优化

思路和 AcWing 1064. 小国王AcWing 327. 玉米田一致,这里不作过多赘述了。

#include<bits/stdc++.h>

using namespace std;
const int N = 110, M = 10, K = 1<<M;
int n, m;
int g[N];
int dp[2][K][K];
vector<int> state;
int cnt[K];

int count(int st)
{
    int cnt = 0;
    while(st) cnt+=st&1, st>>=1;
    return cnt;
}

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

int main()
{
    cin>>n>>m;
    for(int i=0; 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(int i=0; i<n+2; ++i)
    {
        for(auto b : state) //第 i-1 行状态
        {
            for(auto c : state) //第 i 行状态
            {
                for(auto a : state) //第 i-2 行状态
                {
                    if(c&g[i]) continue;
                    if((a&b) || (a&c) || (b&c)) continue;
                    dp[i&1][b][c] = max(dp[i&1][b][c], dp[i-1&1][a][b] + cnt[c]); //进行决策 两者取 max
                }
            }
        }
    }
    
    cout<<dp[n+1&1][0][0]<<endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值