>Link
luogu CF888G
luogu T207971
>Description
有一张 n n n 个点的无向完全图。其中点 i i i 的点权为 a i a_i ai。
两个点 u , v u,v u,v 之间的边的边权为 a u a_u au。
求出这张图的最小生成树。
>解题思路
看到求类似异或的最小值,就会想到用Trie
求最小生成树,要找当前最小的两两异或值
发现在Trie树上,如果两两叶子节点的LCA深度越深,这两个点的异或值就越小,那这两个点就越优先连起来
所以我们在一个节点,找以这个节点为LCA要连起来的两个点,如果这个节点左右子树都非空,那这两个子树内部肯定优先连好了,即这两个子树已经是两个连通块
我们现在找一条价值最小的边把两棵子树连起来就行了。考虑启发式合并,枚举叶子节点数量更少的一棵子树,在另一棵子树的trie上匹配它,找到最小值,然后连起来。这其实就是一个分治的过程
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
找一棵子树里面的所有叶子节点,可以用dfs序来实现,而且显而易见的是不管一棵子树内怎么合并,它们最终都是那几个点并在一起,大小不变 好sb我没思考,一开始还去写了个并查集并来并去,然后就出锅了QwQ
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
#define N 200010
#define inf 2147483648
using namespace std;
int n, cnt, dfn[N * 30], siz[N * 30], ed[N * 30];
int b[N], a[N], t[N * 30][3], tot, dep[N * 30];
LL ans, minn;
void getdfn (int now)
{
if (!t[now][0] && !t[now][1])
{
siz[now] = 1;
dfn[now] = ++cnt;
a[dfn[now]] = ed[now];
return;
}
if (t[now][0]) getdfn (t[now][0]);
if (t[now][1]) getdfn (t[now][1]);
siz[now] = siz[t[now][0]] + siz[t[now][1]];
dfn[now] = min (dfn[t[now][0]], dfn[t[now][1]]);
return;
}
void dfs (int now)
{
if (!t[now][0] && !t[now][1]) return;
if (!t[now][0]) dfs (t[now][1]);
else if (!t[now][1]) dfs (t[now][0]);
else
{
dfs (t[now][0]), dfs (t[now][1]);
int k = 0;
if (siz[t[now][0]] > siz[t[now][1]]) k = 1;
int x, pos;
minn = inf;
for (int i = dfn[t[now][k]]; i <= dfn[t[now][k]] + siz[t[now][k]] - 1; i++)
{
pos = t[now][k ^ 1];
for (int j = dep[pos]; j >= 0; j--)
{
x = (a[i] >> j) & 1;
if (t[pos][x]) pos = t[pos][x];
else pos = t[pos][x ^ 1];
}
minn = min (minn, (LL)a[i] ^ (LL)a[dfn[pos]]);
}
ans += minn;
}
}
int main()
{
memset (dfn, 0x7f, sizeof (dfn));
int now, x;
scanf ("%d", &n);
dep[0] = 30;
for (int i = 1; i <= n; i++)
{
scanf ("%d", &b[i]);
now = 0;
for (int j = 30; j >= 0; j--)
{
x = (b[i] >> j) & 1;
if (!t[now][x]) t[now][x] = ++tot, dep[tot] = j - 1;
now = t[now][x];
}
ed[now] = b[i];
}
getdfn (0);
dfs (0);
printf ("%lld", ans);
return 0;
}