P13008 【MX-X13-T3】题解

P13008 【MX-X13-T3】题解

P13008 【MX-X13-T3】「KDOI-12」只有失去光明,才能逃脱黑暗。

你告诉我这是黄题?

首先容易观察到,操作两次 \(2^{i - 1}\) 等价于操作一次 \(2^{i}\),所以如果后者的代价(\(a_{i}\))比前者的代价(\(2a_{i - 1}\))大,总可以替换过来。因此一开始我们令所有 \(a_{i}\)\(2a_{i - 1}\)\(\min\)

然后 \(x\)\(y\) 的值是不重要的,我们只关心他们的差。而差的正负也是不重要的,因为我们可以翻转一个操作序列中所有操作的符号。所以问题变成了:给定 \(t = |x - y|\),选出若干个 \(2^{i}\)(符号任意,且一个 \(2^{i}\) 可以重复选),最小化价值之和。

在调整了价值之后,可以证明:存在最优操作序列,使得每个 \(2^{i}\) 至多被选择一次。这不难理解:显然选出的 \(2^{i}\) 符号一定相同(否则抵消了),如果选出多于 \(1\) 个,则可以把两个 \(2^{i - 1}\) 换成一个 \(2^i\),这一定不劣。不过当 \(t \ge 2^{k + 1}\) 时会出问题,因为我们必须要选多于 \(1\)\(2^{k}\) 才能凑出 \(t\)。解决办法是简单的:直接令 \(k = 30\),多出来的地方的价值用 \(a_{i} = 2a_{i - 1}\) 递推补足。

接下来的做法和官方题解不太一样。

为了解决这个问题,我们使用数位 dp。为了方便,记 \(t_{i}\) 表示 \(t\) 二进制下第 \(0\) 位到第 \(i\) 位的值,即 \(t \operatorname{and} \, (2^{i + 1}- 1)\)。然后把问题改成:选出的数之和与 \(t\) 的差为 \(0\)。记 \(f_{i, j}\) 表示:处理完第 \(0\) 到第 \((i - 1)\) 位,使得当前选出的数之和与 \(t_{i}\) 的差为 \(j \cdot 2^{i}\) 时的最小代价,其中 \(j \in \{-1, 0\}\)。这有什么道理呢?首先,我们选出的数之和与 \(t_{i}\) 的差必须为 \(2^{i}\) 的整数倍,否则,由于之后能选择的数都是 \(2^{i}\) 的倍数,我们一定不能使得选出的数之和与 \(t\) 之差为 \(0\)。另一方面,考察这个差值的上下界,容易发现 \(j\) 可能的取值只有 \(0\)\(-1\)

\(f(i, j)\) 转移时,枚举 \(2^{i}\) 的决策 \(l \in \{-1, 0, 1\}\)(选正数/不选/选负数),设 \(x\)\(t\) 的二进制表示在 \(2^{i}\) 的取值(\(0\)\(1\)),则选择以后总和与 \(t_{i}\) 的差值为 \((j + l - x) \cdot 2^{i}\)。只有系数为偶数时才能转移,易得转移方程为:

\[f(i + 1, (j + l - x) / 2) \gets f(i, j) + |l| \cdot a^{i} \]

初始化 \(f(0, 0) = 1\),其它为 \(+ \infty\)。答案即为 \(f(k + 1, 0)\)

Code
#include<bits/stdc++.h>

using namespace std;

typedef long long i64;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3f;
constexpr int w = 30;

struct Vector {
    const int n, ofs;
    vector<i64> a;
    Vector(int _n, int _ofs): n(_n), ofs(_ofs), a(n, INF) {}
    i64& operator [] (int x) {
        return a[x + ofs]; // 支持负数下标
    }
};

void solve() {
    int x, y, k;
    cin >> x >> y >> k;
    vector<i64> a(w + 1);
    for(int i = 0; i <= k; i++) {
        cin >> a[i];
    }

    for(int i = 1; i <= k; i++) {
        a[i] = min(a[i], a[i - 1] << 1);
    }

    int t = abs(x - y);
    i64 ans = 0;

    for(int i = k + 1; i <= w; i++) {
        a[i] = 2 * a[i - 1];
    }
    k = w;

    vector<Vector> f(k + 2, Vector(2, 1));
    f[0][0] = 0;
    for(int i = 0; i <= k; i++) {
        int tbit = (t >> i) & 1;
        for(int j: {-1, 0}) {
            if(f[i][j] == INF) continue;
            for(int l: {-1, 0, 1}) {
                if((j + l - tbit) % 2) continue;
                int nxt = (j + l - tbit) / 2;
                f[i + 1][nxt] = min(f[i + 1][nxt], f[i][j] + abs(l) * a[i]);
            }
        }
    }

    ans += f[k + 1][0];
    cout << ans << '\n';
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);

    int t;
    cin >> t;
    while(t--) {
        solve();
    }

    return 0;
}
posted @ 2025-06-30 22:17  DengStar  阅读(67)  评论(0)    收藏  举报