E. XOR Tree
题目描述
给出 n n n个点,每个点有一个值 a i a_i ai,对于每个点,它将找到其余所有点中与其异或和最小的点,并且与之连上双向边,问最少要在给出数组中删掉多少数才能使得剩余的数经过这样的操作能变成一颗树。
思路
同样又是熟悉的最小异或点对的问题,容易联想到CF之前的另一道XOR MST。同样首先最直接的想法是 n 2 n^2 n2的暴力求最小点对,但是显然会T飞。正解是01字典树,同样这题也是基于01字典树去思考。首先是熟悉的思路,对于某个特定01串的最小异或和串,应该是从最高位到最低位去贪心匹配(由于是二进制,所以贪心用1^1消去高位可被证明是最优解)。所以可以发现,我们从高位到低位去遍历字典树时,“1”节点的那颗子树内所存放的串一定是会相互匹配的,由此可以慢慢的可以看出树上问题的雏形。同理“0”节点内也是。然后首先,可以证明无论如何不存在一些节点他们的链可以构成一个环,由此基础上结合之前的结论,我们可以发现,只要让同一个深度上只有一个节点就能使得形成是一颗树,于是遍历一遍就可以了。
代码
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int ans = inf;
int trie[maxn << 5][2];
int cnt[maxn << 5]; //子树结点计数
int tot = 0;
void insert(int x) {
int root = 0;
for (int i = 30; i >= 0; --i) {
int j = (x >> i) & 1;
if (!trie[root][j]) {
trie[root][j] = ++tot;
}
cnt[root]++;
root = trie[root][j];
}
}
void dfs(int u, int dep, int sum) {
if (dep == 30) {
ans = min(ans, sum);
return;
}
int ls = trie[u][0];
int rs = trie[u][1];
if (!ls && !rs)
return;
else if (!rs) { //不用删
dfs(ls, dep + 1, sum);
} else if (!ls) {
dfs(rs, dep + 1, sum);
} else {
dfs(ls, dep + 1, sum + cnt[rs] - 1); //删右子树
dfs(rs, dep + 1, sum + cnt[ls] - 1); //删左子树
}
}
int main() {
int n, x;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
insert(x);
}
dfs(0, 0, 0);
printf("%d\n", ans);
return 0;
}