给你一张完全图,任意两个点之间的边权是 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
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;
}