题目戳这里
题意:给你一个n个顶边和n条边,希望你能够添加或删除一些边,每次操作玩之后都必须保证:1.图是联通的2.图上一个环内的边的权值异或起来等于0,让你求得满足要求的最小的边的权值的和
思路:
假如我们要加边因为要保证异或值等于0,那么我们加的边的权值应该适合原先的两个节点间的边权值和是相同的,删除掉边,为了要保证等于0一定还会在加上一条相同权值的边。这样也就是,两个点之间的边权值是不会改变的,所以这个问题可以转化为,把边权值变为点权值(根节点到当前节点的异或值),由此一个点到另一个点的权值就变成了两个点的异或值。然后在通过连取一些边使得这个生成图的权值最小。
这个问题就变为了求异或最小生成树的问题:
可以用Boruvak算法实现,但我不会,后面再学吧。
还可以用01Trie + 分治的方法实现、具体的关于异或最小生成树相关的讲解参考这篇博客,讲的挺清晰的
代码:
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e5+7;
int trie[MAXN*30][2],idx,a[MAXN];
int cnt,head[MAXN],vis[MAXN];
ll tot;
struct node
{
int next,to,w;
}edge[MAXN*2];
void addedge(int u,int v,int w)
{
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void pre_work(int u)//根节点到当前节点的异或值
{
vis[u] = 1;
for(int i = head[u];i != 0;i = edge[i].next){
int v = edge[i].to;
if(!vis[v]){
a[v] = a[u]^edge[i].w;
pre_work(v);
}
}
}
void insert(int x)
{
int p = 0;
for(int i = 29;i >= 0;i --){
int u = x>>i & 1;
if(!trie[p][u]) trie[p][u] = ++idx;
p = trie[p][u];
}
}
int query(int x)
{
int p = 0,ans = 0;
for(int i = 29;i >= 0;i --){
int u = x>>i & 1;
if(trie[p][u]){
//先走异或值小的
p = trie[p][u];
}
else{
ans += (1<<i);
p = trie[p][!u];
}
}
return ans;
}
void dfs(int l,int r,int depth)
{
if(depth == -1 || l >=r ) return ;
int mid = l-1;
while(mid < r && ((a[mid+1] >> depth) & 1) == 0) mid++;//一直走到找到分开的那个点 分成左右两个集合再开始分治
dfs(l,mid,depth-1);
dfs(mid+1,r,depth-1);
if(mid == l-1 || mid == r) return ;//走到单点
for(int i = l;i <= mid;i ++){
insert(a[i]);//左边集合插入 右边找
}
int num = inf;
for(int i = mid+1;i <= r;i ++){
num = min(num,query(a[i]));// 找到最小的异或值
//printf("%d\n",temp);
}
tot += (ll)num;
//清空trie树
for(int i = 0;i <= idx;i ++){
trie[i][0] = trie[i][1] = 0;
}
idx = 0;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n-1;i ++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
u++,v++;
addedge(u,v,w);
addedge(v,u,w);
}
a[1] = 0;
pre_work(1);
//for(int i = 1;i <= n;i ++)printf("%d ",a[i]);
//printf("\n");
sort(a+1,a+1+n);//保证分治是 两个区间的正确性
dfs(1,n,29);
printf("%lld\n",tot);
return 0;
}