[状压dp] 炮兵阵地(状压dp)

0. 前言

相关:

强相关:

1. 状压dp+棋盘式(基于连通性)

292. 炮兵阵地

在这里插入图片描述在这里插入图片描述

[状压dp] 玉米田(状压dp) 是高度为 1 的十字形,本题是 高度为 2 的十字形。 那么状态转移就和前 2 层有关。且本题是求最大值,而非方案数

思路:

  • 状态定义:
    • f[i][j][k]:摆放完前 i 行,且第 i-1 行摆放状态是 j,第 i 行摆放状态是 k 的所有摆放方案的最大值
  • 状态计算:
    • 假设第 i 行状态是 b,第 i-1 行状态是 a,第 i-2 行状态是 c

    • ((a&b) | (a&c) | (b&c)) = 0 则说明纵向不会出现冲突

    • (g[i-1]&a | g[i]&b)=0 则说明炮兵在平地上。在此与的优先级高于或的优先级,不用加括号

    • 状态转移方程: f[i][j][k] = max(f[i][j][k], f[i-1][c][j]+cnt[c])

  • 时间复杂度: n ∗ 2 m ∗ 2 m ∗ 2 m = O ( n 2 3 m ) = 100 ∗ 2 30 = 1 0 11 n*2^m*2^m*2^m=O(n2^{3m})=100*2^{30}=10^{11} n2m2m2m=O(n23m)=100230=1011 这个时间复杂度…但是里面合法状态很少…依旧可以过

滚动数组优化,不然空间直接爆炸。

重点还是在状态定义及状态计算上。这里状态定义记录了第 i 行状态为 k、第 i-1 行状态为 j。那么找到最后一个不一样的点就是第 i-2 行,所以就依据第 i-2 行进行状态划分即可。

代码:

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

using namespace std;

const int N = 11, M = 1 << 10;

int n, m;
int g[105];
vector<int> state;
int cnt[M];
int f[2][M][M];     // 使用滚动数组,后两维都已经100w了...

bool check(int x) {
    for (int i = 0; i < m; ++i)         // 针对列做状态压缩,同列三个不能有2个1
        if ((x >> i & 1) && ((x >> i + 1 & 1) | (x >> i + 2 & 1)))
            return false;
    
    return true;
}

int count(int x) {
    int res = 0;
    for (int i = 0; i < m; ++i) res += x >> 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;                     // 拿int类型读入char,读入全是0
            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 = 1; i <= n + 2; ++i)
        for (int j = 0; j < state.size(); ++j)              // j为第i-1行状态
            for (int k = 0; k < state.size(); ++k)          // k为第i行状态
                for (int u = 0; u < state.size(); ++u) {    // u为第i-2行状态
                    int a = state[j], b = state[k], c = state[u];
                    if ((a & b) | (b & c) | (a & c)) continue; 
                    if (g[i - 1] & a | g[i] & b) continue;
                    f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
                }
                
    cout << f[n + 2 & 1][0][0] << endl;
    
    
    // 遍历写法,修改第一个 for (int i = 1; i <= n; ++i)
    /*
    int res = 0;
    for (int i = 0; i < state.size(); ++i) 
        for (int j = 0; j < state.size(); ++j)
            res = max(res, f[n & 1][i][j]);
    cout << res << endl;
    */
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值