2020牛客暑期多校训练营(第五场)B.Graph(boruvka算法 完全图最小生成树/异或01字典树)

探讨在具有特定丑陋值的树形结构中,通过加边或删边操作达到连通且满足环上边的异或和为0的条件下,如何利用Boruvka算法寻找最小生成树,实现边的丑陋值之和最小化。

题目

n(n<=1e5)个点的树,每条边有一个丑陋值v,

你可以对这棵树加边或删边,但是任意时刻都应满足如下条件:

①图是连通的②对于图中任意一个环,环上的边的丑陋值的异或和为0

经若干次加边删边之后,问得到的图中剩下的边的丑陋值之和最小是多少

思路来源

题解

考虑,对于树上加一条u-v边成环,

这一条边的权值,就应该是u到lca边的权值异或上v到lca的边的权值,这样才能成0,

不妨再异或上两次lca到根rt的边的权值,抵消掉,这样方便处理成到根的情形

记ei为点i到根的边异或出来的权值,

则原问题等价为,在一个完全图中,连接i和j的代价为ei^ej,求完全图最小生成树

于是就是CF888G的原题,用Boruvka算法求最小生成树,

该算法类似Kruskal,思路是,设当前还有x个连通块,

则每次遍历m条边,对于每个连通块找到连向其他连通块的最小边权的边号,用这些边合并连通块

由于连通块每次会至少减半,复杂度是O((m+n)logn)

将n个权值插入字典树,字典树上的话,则若某个节点左右各有数值,

则先将左右各处理成一个连通块,然后两个连通块各取一个值ei、ej出来,使ei^ej最小,连通两个块

重复这个过程至最后只剩一个连通块,复杂度大概是O(n*30*logn),点数*找异或最优*合并次数

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> P;
const int N=1e5+10,M=30*N;
int n,u,v,w,a[N],tr[M][2],id[M],c;
vector<P>e[N];
ll ans;
void dfs(int u,int fa){
    for(P x:e[u]){
        int v=x.fi,w=x.se;
        if(v==fa)continue;
        a[v]=a[u]^w;
        dfs(v,u);
    }
}
void ins(int v,int y){
    int rt=0;
    for(int j=29;j>=0;--j){
        int x=v>>j&1;
        if(!tr[rt][x]){
            tr[rt][x]=++c;
            tr[c][0]=tr[c][1]=id[c]=0;
        }
        rt=tr[rt][x];
    }
    id[rt]=y;
}
int unite(int x,int y,int d){
    if(d==0){
        return a[id[x]]^a[id[y]];
    }
    int res=1<<30;
    bool zero=0;
    for(int i=0;i<2;++i){
        if(tr[x][i] && tr[y][i]){
            res=min(res,unite(tr[x][i],tr[y][i],d-1));
            zero=1;
        }
    }
    if(!zero){
        if(tr[x][0] && tr[y][1]){
            res=min(res,unite(tr[x][0],tr[y][1],d-1));
        }
        else if(tr[x][1] && tr[y][0]){
            res=min(res,unite(tr[x][1],tr[y][0],d-1));
        }
    }
    return res;
}
void cal(int u,int d){
    int ls=tr[u][0],rs=tr[u][1];
    if(ls)cal(ls,d-1);
    if(rs)cal(rs,d-1);
    if(ls && rs)ans+=unite(ls,rs,d-1);
}
int main(){
    scanf("%d",&n);
    for(int i=2;i<=n;++i){
        scanf("%d%d%d",&u,&v,&w);
        u++;v++;
        e[u].push_back(P(v,w));
        e[v].push_back(P(u,w));
    }
    dfs(1,-1);
    for(int i=1;i<=n;++i){
        ins(a[i],i);
    }
    cal(0,30);
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值