题目大意
给你一张完全图,任意两个点之间的边权是 a i ⊕ a j a_i\oplus a_j ai⊕aj.问你最小生成树大小
题目思路
看到异或位运算。自然想到字典树.将所有点插入到字典树.看看效果
性质:
令
S
i
S_i
Si为节点
i
i
i的所有叶子节点在图中所构成的连通块.
1.图中任意两点连边,等价于树上的对应叶子节点
l
c
a
lca
lca往下的花费。所以
L
C
A
LCA
LCA越深越好
2.任意一个节点,若存在两个儿子
a
,
b
a,b
a,b,那么对于将
S
a
,
S
b
S_a,S_b
Sa,Sb联通时,这一位的花费是固定不可避免的.
反之,若只存在一个儿子,连通
S
a
S_a
Sa 本身,这一位的花费是没有的。
所以容易设计出暴力+贪心的方法:
dfs从高位开始遍历所有 含有两个儿子
a
,
b
a,b
a,b的节点,然后递归的在
a
,
b
a,b
a,b子树里贪心的尽量移动同样的位值。使得花费最小。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
解释:
最差情况就是 字典树是一颗完全二叉树.这个时候需要枚举n个节点。
递归的复杂度是节点子树大小.
O
(
∑
s
z
(
i
)
)
=
O
(
∑
d
e
p
(
i
)
)
=
O
(
∑
i
=
0
l
o
g
n
2
i
∗
i
)
=
O
(
n
l
o
g
n
)
O(\sum sz(i))=O(\sum dep(i))=O(\sum_{i=0}^{logn}2^i*i)=O(nlogn)
O(∑sz(i))=O(∑dep(i))=O(∑i=0logn2i∗i)=O(nlogn)
算法正确性:
根据Brouka算法,
最开始叶子节点合并时,只有当他们不在同一个点时,会产生花费。
第一轮合并连通块后。在字典树上缩点。递归论证.
不难看出上述算法就是Brouka算法的过程。只是逆过来了更好处理。
当然也可以直接模拟B算法,但是需要支持动态开点+合并操作的字典树。复杂度也会比较高.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int up = 30;
int a[maxn];
namespace Trie{
int tr[maxn * 20][2] , cnt;
void add (int x){
int u = 0;
for (int i = up ; i >= 0 ; i--){
int v = (x >> i) & 1;
if (!tr[u][v]) tr[u][v] = ++cnt;
u = tr[u][v];
}
}
int ask (int u , int v , int step){
if (step == -1) return 0;
int x = -1 , y = -1;
if (tr[u][0] && tr[v][0]) x = ask(tr[u][0],tr[v][0],step-1);
if (tr[u][1] && tr[v][1]) y = ask(tr[u][1],tr[v][1],step-1);
if (~x && ~y) return min(x , y);
if (~x) return x; if (~y) return y;
x = y = -1;
if (tr[u][0] && tr[v][1]) x = ask(tr[u][0],tr[v][1],step-1) + (1 << step);
if (tr[u][1] && tr[v][0]) y = ask(tr[u][1],tr[v][0],step-1) + (1 << step);
if (~x && ~y) return min(x , y);
if (~x) return x;
return y;
}
ll dfs (int u , int step){
if (step == -1) return 0;
ll ans = 0;
if (tr[u][0] && tr[u][1]){
ans += 1ll * ask(tr[u][0] , tr[u][1] , step - 1) + (1ll << step);
}
if (tr[u][0]) ans += dfs(tr[u][0] , step - 1);
if (tr[u][1]) ans += dfs(tr[u][1] , step - 1);
return ans;
}
}
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
for (int i = 1 ; i <= n ; i++) {
int x;cin >> x;
Trie::add(x);
}
cout << Trie::dfs(0 , up) << endl;
return 0;
}