P13008题解

传送门:https://www.luogu.com.cn/problem/P13008

题意:将 \(x\) 增减若干次 \(2^i\) 使其与 \(y\) 相等。其中每次修改的代价为 \(a[i]\) ,求最小总代价。

题解:首先显然只需要提供增减使 \(x=y\) ,我们只需要将两者差值的绝对值 \(z\),通过二进制修改至 \(0\) 即可。先考虑 \(z=2^i\) 的特殊情况,可以通过减少 \(2\)\(2^{i-1}\),花费减少两次 \(2^{i-1}\) 的代价;或直接花费 \(a[i]\) 的代价直接减少 \(2^i\)

\(f[i]\) 表示将 \(z\) 减少 \(2^i\) 的最小代价,即消除第 \(i\) 位二进制上的 \(1\) 所花费的最小代价。

于是有如下状态转移方程:

\[\begin{cases} f[i] = a[i] & (i = 0) \\ f[i] = min(f[i - 1] * 2, a[i]) & (i \geq 1) \end{cases} \]

对于一般情况,我们消除所有二进制位置上的 \(1\) 。消除某一特定位置的 \(1\) 我们有两种方法。一种是直接花费 \(f[i]\) 将该位置的 \(1\) 改为 \(0\) 。 第二种方法:若有若干个连续的 \(1\) ,假设开头为第 \(i\) 位,结尾为第 \(j\) 位。我们可以花费 \(f[j]\)\(z\) 加上 \(2^j\) 使得 \(i\) ~ \(j\) 区间所有的 \(1\) 全部都改为 \(0\),而第 \(i - 1\) 位则改为 \(1\)。但对于出现连续的 \(1\) 贪心应用第二种情况是错误的(一开始看到贪心标签是这么认为的)。例如 \(z=3\)\(a[i] = {1,2,4}\) ,此时应用第一种方法答案是 \(1+2=3\) ,而第二种是 \(1+4=5\) 。所以何时对连续 \(1\) 段采用进位,甚至进位的位置都是不确定的。

进一步发现,如果我们将某一位成功修改为 \(0\) ,我们显然不需要再次刻意修改该位。也就是说某一位的修改只和后一位是否进位有关,而不与后若干位具体的修改方式有关。于是再次使用动态规划解决这个问题。

\(g[i][0/1]\) 表示将第 \(i\) 位以及所有比 \(i\) 小的位全部设为 \(0\) 的最小代价,\(0/1\) 表示当前是否往前进位。 $ q[i] $ 表示 \(z\) 的第 \(i\) 的二进制位的值。

有如下转移方程:

\[\begin{cases} g[i][0] = 0 & (i = 0) \\ g[i][1] = inf & (i = 0) \\ g[i][0] = min(g[i-1][0],g[i-1][1]+f[i])& (i \ge 1且q[i]=0) \\ g[i][0] = g[i-1][0]+f[i] & (i \geq 1且q[i]=1) \\ g[i][1]=g[i-1][1]+f[i] & (i\ge1且q[i]=0) \\ g[i][1]=min(g[i-1][0]+f[i],g[i-1][1]) & (i \geq 1且q[i]=1) \end{cases} \]

其中不存在 \(1\) 往前一个 \(1\)\(1\) 进位,前一位未进位的情况。刻意将 \(0\) 修改两次向前进 \(1\) 显然不优,因为这个方案已经被 \(f\) 数组连续两次贡献给前一位仅 \(1\) 所考虑,可以放弃通过进位改为交给前一位利用处理 \(f\) 数组处理。使用省略这种情况。具体转移过程十分显然,不再赘述。

#include <bits/stdc++.h>

using namespace std;

#define int long long

int a[32], f[32];

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        int x, y, k;
        cin >> x >> y >> k;
        for (int i = 0; i <= k; ++i) cin >> a[i];
        f[0] = a[0];
        for (int i = 1; i <= 31; ++i) {
            if (i <= k) f[i] = min(f[i - 1] * 2, a[i]);
            else f[i] = f[i - 1] * 2;
        }
        int z = abs(x - y);
        int q[32];
        int cnt = 0;
        for (; z; z >>= 1) q[cnt++] = z & 1;
        int a = 0, b = 0x3f3f3f3f;
        for (int i = 0; i < cnt; ++i) {
            if (!q[i]) a = min(b + f[i], a), b += f[i];
            else a += f[i], b = min(b, a);
        }
        cout << min(a, b + f[cnt]) << '\n';
    }
    return 0;
}
posted @ 2025-07-11 10:22  Jefferyzzzz  阅读(7)  评论(0)    收藏  举报