P13008 【MX-X13-T3】题解
P13008 【MX-X13-T3】题解
你告诉我这是黄题?
首先容易观察到,操作两次 \(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(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;
}

浙公网安备 33010602011771号