CF888-G. Xor-MST-(分治+01字典树,最小异或生成树)

在01字典树上建点。
然后考虑怎么使得生成树最小。
从高位开始,按当前为1/0将所有数划分为两个集合(两者都不为空的情况下)。
这两个集合是分散的,所以我们需要一条边连接它们。
暴力枚举选取。
但是这样会遇到一个问题,即如何维护一段连续的区间,使得这个区间中的所有元素都在这个集合内。
我们只要对a数组排序,然后依次插入字典树即可。
所以我们对于树上的每个节点,记录对应的数组区间序号。
L数组和R数组表明当前节点对应数组的最小序号和最大序号。
分治暴搜即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 350000
#define MAXM 6000005
int n,k;
int tot=0;
int a[MAXN];
int tree[MAXM][2];
int l[MAXN];
int r[MAXN];
ll res=0;
void insert(int j)
{
    
    
    int rt=0;
    for(int i=29;i>=0;i--)
    {
    
    
        int cur=(a[j]>>i)&1;
        if(tree[rt][cur]==0){
    
    
            tree[rt][cur]=++tot;
            l[tot]=r[tot]=j;
        }
        rt=tree[rt][cur];
        l[rt]=min(l[rt],j);
        r[rt]=max(r[rt],j);
    }
}
int fin(ll x,ll rt,ll dep)
{
    
    
    int res=0;
    for(int i=dep;i>=0;i--)
    {
    
    
        int cur=(x>>i)&1;
        if(tree[rt][cur]==0)
        {
    
    
            res|=(1<<i);
            cur^=1;
        }
        rt=tree[rt][cur];
    }
    return res;
}

void solve(ll rt,int dep)
{
    
    
    if(dep==-1)return ;
    int ls=tree[rt][0],rs=tree[rt][1];
    if(ls&&rs){
    
    
        int cmp=2e9;
        for(int i=l[ls];i<=r[ls];i++){
    
    
            cmp=min(cmp,fin(a[i],rs,dep-1));
        }
        res+=(1<<dep);
        res+=cmp;
    }
    if(ls)solve(ls,dep-1);
    if(rs)solve(rs,dep-1);
}

int main()
{
    
    
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
        insert(i);
    solve(0,29);
    printf("%lld\n",res);
}

猜你喜欢

转载自blog.csdn.net/weixin_43353639/article/details/107648529