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

被折叠的 条评论
为什么被折叠?



