2020牛客暑期多校训练营Graph(boruvka,字典树,贪心,异或最小生成树)

Graph

题目描述

2020牛客暑期多校训练营Graph(boruvka,字典树,贪心,异或最小生成树)_第1张图片

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

6
0 1 1
1 2 4
1 3 3
0 4 5
0 5 2

输出

7

题目大意

给定一棵树,现你可以进行加边或者删边的操作。
要求任何时刻,都满足一下两点:
1 、 1、 1图都是连通的。
2 、 2、 2每个环的 X O R XOR XOR和为 0 0 0
要求,对于这棵树进行若干次操作后,边权值和的最小值是多少。

分析

首先暴力是肯定不行的。那么就从两个操作入手。
1 、 1、 1要求图连通,这个很容易。
2 、 2、 2 X O R XOR XOR和为0,那么首先考虑一点: a   x o r   a = 0 a\,xor\,a=0 axora=0,因此我们只要把任意一个环都转化为两个相等的数相 X O R XOR XOR即可。那么对于你连成一个环的时候,你连上的边的长度和已经存在的边的 X O R XOR XOR和相等,就可以保证 X O R XOR XOR和为 0 0 0
而对于一棵树,将两个节点连起来的环就是这两个被连的节点到根的链与加上去的边形成的环。所以我们只要把每个节点到根节点的 X O R XOR XOR和算出来存入 a [ ] a[] a[]中,如果要连接,就只要将这两个点 i , j i,j i,j相连的边长设为 a [ i ]   x o r   a [ j ] a[i]\,xor\,a[j] a[i]xora[j]即可。

如此,我们便可以任意建出我们想要的样子,这样,我们就得到了一张完全图,然后实际上就是求其中的最小生成树。但是无论是用Kruskal还是Prim,都会超时的。所以要用到boruvka求最小生成树。它就是对于每个点,枚举一遍求出合并后得到权值最小的方法,然后合并。那么对于这题,我们只要建一个01字典树,然后可以发现,我们可以求出最长的相同的部分,可以使得两个数的 X O R XOR XOR尽可能小。

那么接下来我们就可以看代码了,用字典树维护每个集合之间的最长的公共部分。

代码

#include
#define ll long long
using namespace std;
const int MAXN=100010;
struct Edge{
	int v,w,nxt;
	Edge(){}
	Edge(int vv,int ww,int nnxt){
		v=vv,w=ww,nxt=nnxt;
	}
}e[MAXN<<1];//邻接表
int n,tot=0;
ll cnt=0,ans=0,a[MAXN<<1],trie[MAXN*32][2],d[MAXN*32],p[MAXN*32],h[MAXN];
void add(int x,int y,int z){
	e[++tot]=Edge(y+1,z,h[x+1]);
	h[x+1]=tot;
}
void dfs(int pos,int fa){
	for(int i=h[pos];i;i=e[i].nxt){
		if(e[i].v==fa) continue;
		a[e[i].v]=a[pos]^e[i].w;dfs(e[i].v,pos);
	}
}//遍历树求每个节点到root的XOR和
ll find(int x,int y){
	if(d[x]==30) return a[p[x]]^a[p[y]];
	bool fl=0;ll ret=2e9;
	for(int i=0;i<=1;i++)
		if(trie[x][i]&&trie[y][i])
			ret=min(ret,find(trie[x][i],trie[y][i])),fl=1;
	if(!fl)
		if(trie[x][0]&&trie[y][1]) ret=min(ret,find(trie[x][0],trie[y][1]));
		else if(trie[x][1]&&trie[y][0]) ret=min(ret,find(trie[x][1],trie[y][0]));
	return ret;
}//找到最小的合并
void insert(int k){
	int x=1;
	for(int i=29;i>=0;i--){
		bool u=(1<<i)&a[k];
		if(!trie[x][u]) trie[x][u]=++cnt,d[cnt]=30-i,p[cnt]=n+1;
		x=trie[x][u];
	}p[x]=k;
}//建树
void dfs1(int x){
	if(x==0) return;
	dfs1(trie[x][0]);dfs1(trie[x][1]);//继续递归
	if(trie[x][0]&&trie[x][1]) ans+=find(trie[x][0],trie[x][1]);//合并
}
int main()
{
	int u,v,w;
	scanf("%d",&n);p[1]=cnt=1;
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}a[1]=0;dfs(1,0);
	for(int i=1;i<=n;i++) insert(i);
	dfs1(1);printf("%lld",ans);
}

END

要素过多。有错即评。

你可能感兴趣的:(2020牛客多校,boruvka,字典树,最小生成树)