题解:P15245 [WC2026] 二进制
题意
给定两个正整数 \(x,y\),每次操作可以:
- \(x\gets 2x\)。
- \(x\gets x+1\)。
- \(y\gets 2y\)。
- \(y\gets y+1\)。
求最小操作次数使得 \(x=y\)。多测,\(T\leq 5\times 10^7\),\(x<y\leq 10^{18}\)。
题解
场外选手来凑一下热闹,做了好久哦。
下文中将 __builtin_popcount 的时间复杂度视作 \(\mathcal{O}(\log\log{V})\)。
观察操作,发现每一次 \(\times 2\) 操作之后至多进行一次 \(+1\) 操作。也就是说,我们的操作必然形如:给操作数 \(a\) 一直 \(+1\) 变成 \(a'\),然后一直做 \(\times 2,(+1),\times 2,(+1),\cdots\) 的操作。从二进制表示来看,相当于给 \(a'\) 拼了一段后缀。
同时可以看出,我们一定不会给 \(y\) 进行 \(\times 2\) 操作,因为如果给 \(y\) 拼了某段后缀,\(x\) 也一定会拼(容易证明把 \(x\) 操作到 \(\geq 2x\) 的数必定会进行 \(\times 2\) 操作),此时我们同时把这段后缀对应的操作删去显然更优。
不妨考察如何计算将 \(x\) 操作成 \(y\) 的最小操作次数 \(f(x,y)\)。枚举 \(y\) 的长度为 \(i\) 的后缀,表示 \(x\) 恰好拼上了这段后缀,可以得到:
这样我们可以单次 \(\mathcal{O}(\log{V}\log\log{V})\) 地计算 \(f(x,y)\)。
进一步可以证明,这个函数的最小值一定在 \(i=\lfloor\log_2(y/x)\rfloor\) 处取到。
证明
考察相邻项的差值:
显然在 \(i\) 的定义域内 \(\Delta_i\geq 0\),因此最小值在 \(i=\lfloor\log_2(y/x)\rfloor\) 处取到。\(\Box\)
这样我们可以单次 \(\mathcal{O}(\log\log{V})\) 地计算 \(f(x,y)\) 了。
如果最终将 \(x,y\) 都操作成了 \(y+d\),则操作次数为 \(f(x,y+d)+d\)。设 \(k=\lfloor\log_2(y/x)\rfloor\),考察 \(f(x,2^{k+1}x)=k+1\),容易发现对于 \(t>2^{k+1}x\),必然有 \(f(x,t)>k+1\)。于是我们可以将 \(k+1+2^{k+1}x-y\) 计入答案,接下来只用考虑 \(\lfloor\log_2((y+d)/x)\rfloor=k\) 的情况。
由于固定了 \(\lfloor\log_2((y+d)/x)\rfloor\),\(f(x,y+d)-f(x,y)\) 必然 \(\leq\log_2{V}\)。这是因为只有 \(\operatorname{popcnt}\) 能使得函数值减小。于是我们只需要枚举 \(0\leq d\leq \log_2{V}\),将答案对 \(f(x,y+d)+d\) 取 \(\min\) 即可。至此我们得到了 \(\mathcal{O}(T\log{V}\log\log{V})\) 的做法。期望得分 \(84\ \text{pts}\)。
进一步优化,随着 \(d\) 增大,我们肯定想要 \(\operatorname{popcnt}\) 减小。从 \(y\) 开始,每次令 \(y\gets y+\operatorname{lowbit}(y)\) 即可,加到 \(y\bmod{64}=0\) 就不用再加了。时间复杂度优化至 \(\mathcal{O}(T\log^2\log{V})\),期望得分 \(96\ \text{pts}\)。但是以我的实现已经可以在 QOJ 上通过本题。
继续挖掘性质。我们知道 \(+\operatorname{lowbit}\) 的本质是推平最靠右的 \(1\) 段,可以发现不断 \(+\operatorname{lowbit}\) 的过程中,除了最后一次操作,其他操作给 \(\operatorname{popcnt}\) 带来的增量显然 \(\leq 6\)。更具体来说,如果不是最后一次操作,且增量为 \(2^k(k\geq 2)\),那就无法带来正收益。因此我们只需要特判一下最后一次操作,然后依次考虑能否 \(+1,+2\) 即可。这样加上 \(f(x,y)\) 这次计算,只会计算 \(4\) 次 \(\operatorname{popcnt}\)。
伟大的 @KazamaRuri 发现同时 \(+1,+2\) 同样无法带来正贡献,因此可以压到 \(3\) 次 \(\operatorname{popcnt}\),跑得挺快。
其实可以直接预处理出低 \(6\) 位处 \(\operatorname{popcnt}(x+i)+i\) 的增量极值,这样就只有 \(2\) 次 \(\operatorname{popcnt}\) 了。代码跑得飞快,可以轻松通过本题。
这样我们就得到了 \(\mathcal{O}(T\log\log{V})\) 的做法。
代码
#include "binary.h"
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<typename T> inline void chk_min(T &x, T y) { if (y < x) x = y; }
template<typename T> inline void chk_max(T &x, T y) { if (x < y) x = y; }
int df[64];
void init(int t, int c) {
for (int x = 0; x < 64; ++x) {
int pc = __builtin_popcount(x);
for (int i = 0; i < 6; ++i) chk_max(df[x], pc - __builtin_popcount(x + i) - i);
}
}
ll binary(ll x, ll y) {
int k = 63 ^ __builtin_clzll(y / x), z = y & 63;
ll mask = (1ll << k) - 1, res = (x << k + 1) - y + k + 1;
auto calc = [&](ll y) { return (y >> k) - x + __builtin_popcountll(y & mask) + k; };
int p = z & 32 ? 64 - z : 0;
if (p) chk_min(res, calc(y + p) + p);
chk_min(res, calc(y) - df[y & mask & 63]);
return res;
}

浙公网安备 33010602011771号