AtCoder ABC 328G 状态压缩 DP + 复杂度分析

155 篇文章 1 订阅
题意

传送门 AtCoder ABC 328G Cut and Reorder

题解

假设答案对应的 a a a 下标 0 , 1 , ⋯   , n − 1 0,1,\cdots,n - 1 0,1,,n1 经过操作 1 变换为排列 p 0 , p 1 , ⋯   , p n − 1 p_{0},p_{1},\cdots,p_{n-1} p0,p1,,pn1,则对于满足 p i − 1 ≠ p i , 0 < i p_{i-1}\neq p_{i},0<i pi1=pi,0<i 的位置,都至少要分割一次,且仅需这样的分割就可以得到 a p 0 , a p 1 , ⋯   , a p n − 1 a_{p_{0}},a_{p_{1}},\cdots,a_{p_{n-1}} ap0,ap1,,apn1。则状态压缩优化 p p p 的枚举即可。

不考虑分割贡献,则需要维护两个变量:集合 A = { a p 0 , a p 1 , ⋯   , a p m } \mathcal{A}=\{a_{p_{0}}, a_{p_{1}}, \cdots,a_{p_{m}}\} A={ap0,ap1,,apm},以及集合匹配的 b b b 的前缀的长度 m m m。实际上,只需要维护前者,因为 ∣ A ∣ = m \lvert\mathcal{A}\rvert = m A=m

容易想到两类计算分割贡献的方案。一类是新增状态 a p i − 1 a_{p_{i-1}} api1,转移时可以直接判断 a p i − 1 ≠ a p i a_{p_{i-1}}\neq a_{p_{i}} api1=api,时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)。另一种方案是枚举分割的段,此时每一个连续的段(除了起始段)操作 1 贡献是 c c c A \mathcal{A} A 状态数为 2 n 2^n 2n,对于每一个状态,可能的段规模为 O ( n 2 ) O(n^2) O(n2),时间复杂度也是 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn);实际上,这是一个宽松的界,对于每一个段的长度 k k k,实际上对应的状态数为 2 n − k 2^{n-k} 2nk,则一个更紧的界为 O ( ∑ k 2 n − k ) = O ( n log ⁡ n ) O(\sum k2^{n-k})=O(n\log n) O(k2nk)=O(nlogn)

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    long long c;
    cin >> c;
    vector<long long> a(n), b(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    for (int i = 0; i < n; ++i) {
        cin >> b[i];
    }
    auto get_min = [&](long long &x, long long y) {
        x = min(x, y);
    };
    const long long inf = 1e18;
    vector<long long> dp(1 << n, inf);
    dp[0] = 0;
    for (int i = 0; i < 1 << n; ++i) {
        int bi = __builtin_popcount(i);
        for (int l = 0, r = 0; l < n; l = r) {
            if (i >> l & 1) {
                r = l + 1;
                continue;
            }
            while (r < n && (~i >> r & 1)) {
                r += 1;
            }
            for (int ai = l; ai < r; ++ai) {
                int st = 0;
                long long add = 0;
                for (int d = 1; ai + d <= r; ++d) {
                    st |= 1 << (ai + d - 1);
                    add += abs(a[ai + d - 1] - b[bi + d - 1]);
                    long long split = bi == 0 ? 0 : c;
                    get_min(dp[i | st], dp[i] + add + split);
                }
            }
        }
    }
    cout << dp[(1 << n) - 1] << '\n';

    return 0;
}
  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值