题目描述

  给定 \(n\) 个点的无向完全图。每个点的点权为 \(a_i\),连接点 \(i\) 和点 \(j\) 的边的边权为 \(a_i\oplus a_j\),求这个图的最小生成树的权值\((1\leq n\leq 2\times 10^5,0\leq a_i< 2^{30})\)

分析

  由于不可能预处理出所有边的边权,而且边权与异或有关,所以把样例一从高位到低位插入字典树中观察:

  可以发现对于每个叶子节点(即点权)之间,如果需要得到相互连边后的边权,求它们的 $\text{LCA} $ 之后的异或值即可。

  因此如果两点间的 $\text{LCA} $ 深度越大,边权值便越小。因为数字从高位到低位插入字典树,所以深度越小的 $\text{LCA} $ 连边后的边权越大,尽量避免选 $\text{LCA} $ 小的点。

  可以发现恰好有 \(4\)\(\text{LCA }\),也就是有两个儿子的节点一共有 \(4\) 个,如果 \(a_i\) 两两不等,那么这样的点一共有 \(n-1\) 个,对应最小生成树的 \(n-1\) 条边。

  每找到有两个儿子的点,就贪心 \(dfs\) 下去:

  \(1.\) 每次都尽量同时走左儿子/右儿子。

  \(2.\) 如果左右儿子都有,就两个儿子都走,返回值取 \(\min\)

  \(3.\) 如果两个点只有不同的儿子,就在返回值上加上 \(2^{bit}\),然后继续走。

  最后的答案就是 \(dfs\) 的和。

  如果 \(a_i\) 之间不是两两不等,则直接连接点权相同的 \(a_i\),边权为 \(0\),对答案没有影响。

  根据启发式合并的思想,每次连边的时间复杂度等于 树的深度 \(\times\) 小的子树大小,所以 \(dfs\) 与连边的时间复杂度不会超过 \(O(n\log^2 n)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int trie[N*30][2],a[N],tot=0;
void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        int ch=(x>>i)&1;
        if(!trie[p][ch])
            trie[p][ch]=++tot;
        p=trie[p][ch];
    }
}
int solve(int root1,int root2,int bit)
{
    if(bit<0)
        return 0;
    int ans1=-1,ans2=-1;
    if(trie[root1][0]&&trie[root2][0])
        ans1=solve(trie[root1][0],trie[root2][0],bit-1);
    if(trie[root1][1]&&trie[root2][1])
        ans2=solve(trie[root1][1],trie[root2][1],bit-1);
    if(ans1>=0&&ans2>=0)
        return min(ans1,ans2);
    if(ans1>=0)
        return ans1;
    if(ans2>=0)
        return ans2;
    if(trie[root1][0]&&trie[root2][1])
        ans1=solve(trie[root1][0],trie[root2][1],bit-1)+(1<<bit);
    if(trie[root1][1]&&trie[root2][0])
        ans2=solve(trie[root1][1],trie[root2][0],bit-1)+(1<<bit);
    if(ans1>=0&&ans2>=0)
        return min(ans1,ans2);
    if(ans1>=0)
        return ans1;
    if(ans2>=0)
        return ans2;
}
long long ans=0;
void dfs(int start,int bit)
{
    if(bit<0)
        return ;
    if(trie[start][0]&&trie[start][1])
        ans=ans+1ll*solve(trie[start][0],trie[start][1],bit-1)+(1<<bit);
    if(trie[start][0])
        dfs(trie[start][0],bit-1);
    if(trie[start][1])
        dfs(trie[start][1],bit-1);
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        insert(a[i]);
    }
    dfs(0,30);
    cout<<ans<<endl;
    return 0;
}
posted on 2020-10-13 21:54  ResuscitatedHope  阅读(162)  评论(0编辑  收藏  举报