题目描述
给定一个长度为 \(n\) 的序列 \(a\)。挑出两个数 \(a_x,a_y(x\neq y)\) 使得让 \(a_x=a_y\) 的最小操作数最大。
一次操作是选择一个非负整数 \(k\),使得 \(2^k\geq a_i\),然后令 \(a_i\leftarrow 2^k-a_i\)。
\(2\le n\le 2\cdot 10^5,0\le a_i\le 10^9\)。
有史以来最简单的 \(\color{red}{\text{Div2 }}\color{purple}{\text{E}}\)。
首先可以考虑若已知 \(a_x,a_y\),让 \(a_x=a_y\) 大概是怎样的操作。将这个操作对应到二进制上相当于是将 \(\text{lowbit}(a_i)\) 前面的位全部取反,且操作是可逆的。
操作过程即是 \(a_x\) 先不断变小一段\((\)可能为空\()\),再不断变大一段\((\)可能为空\()\)。
不妨设 \(a_x>a_y\),若 \(a_x\) 二进制中最高位为 \(1\) 但在 \(a_y\) 中却没有此位那么显然 \(k\) 不会取到最高位前面去,不然就会产生多余的 \(1\),还需要额外的操作来抵消。
由于操作可逆,可以考虑建图,只需要考虑每个 \(a_i\) 变成 \(0\) 的过程。此时每次操作必定会用到最小的 \(k\) 满足 \(2^k\ge a_i\),再对 \(a_i\) 进行操作。在操作前后的 \(a_i\) 之间建边,单个数的操作数是 \(O(\log a_i)\) 的,所以总点数为 \(O(n\log \max a_i)\)。
于是题目转化成找这 \(n\) 个点在树上两两距离的最大值,两遍 \(dfs\) 即可。时间复杂度 \(O(n\log \max a_i)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 6e6 + 5, M = 2e5 + 5;
int n, tot, a[M], dep[N]; vector <int> G[N];
unordered_map <int, int> mp;
inline int op(int x) {
int pw = log2(x);
if ((1 << pw) >= x) return (1 << pw) - x;
return (1 << pw + 1) - x;
}
inline void dfs(int x, int fa) {
dep[x] = fa ? dep[fa] + 1 : 0;
for (auto y : G[x]) if (y ^ fa) dfs(y, x);
}
int main() {
scanf("%d", &n), mp[0] = tot = 1;
for (int i = 1; i <= n; ++ i) {
scanf("%d", a + i); int p = a[i]; if (mp[p]) continue;
mp[p] = ++ tot; int nxt = op(p);
while (!mp[nxt]) {
mp[nxt] = ++ tot, G[mp[nxt]].emplace_back(mp[p]), G[mp[p]].emplace_back(mp[nxt]);
p = nxt, nxt = op(p);
}
G[mp[nxt]].emplace_back(mp[p]), G[mp[p]].emplace_back(mp[nxt]);
}
dfs(mp[a[1]], 0); int p = 1; for (int i = 2; i <= n; ++ i) if (dep[mp[a[i]]] > dep[mp[a[p]]]) p = i;
dfs(mp[a[p]], 0); int q = 1; for (int i = 2; i <= n; ++ i) if (dep[mp[a[i]]] > dep[mp[a[q]]]) q = i;
return printf("%d %d %d\n", p, q, dep[mp[a[q]]]), 0;
}