先看CF的这道题:

题意:

传送门

有n个点,每个点有一个权值 a i a_i ai,任意两点之间边的权值是这两点权值的异或和,求最小生成树

n ≤ 2 e 5 , a i < 2 30 n \leq 2e5, a_i< 2^{30} n2e5,ai<230

Solution:

首先需要先介绍一种叫做Boruvka的最小生成树算法,算法流程类似Kruskal,对于每个连通块,在他和其他连通块之间找一个最小的边相连,因为每次都会合并一半的连通块,所以复杂度为 O ( m log ⁡ n ) O(m \log n) Omlogn

那么这个算法在这道题里面怎么用呢?

我们先把所有个点都存到一个trie树里面(从高位到低位),然后我们考虑trie树上每个子树里面的点,在若干次合并之后,这些点一定可以先形成一个连通块,因为选子树外的点和子树内的点连边一定是不优于选两个子树内的点连边的。通过这个性质我们就可以得到一个基本的思路:dfs整个trie树,然后进行启发式合并,启发式合并的过程相当于合并左右子树的连通块,就是在左边找一个点,右边找一个点,使得他们的异或和最小,枚举左子树所有的点,在右子树找对应的最小值,最后取min即可,复杂度为 O ( n log ⁡ n log ⁡ a ) O(n\log n\log a) O(nlognloga),a是权值大小。

小trick:对权值排序后,枚举子树的点会更加方便。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,a[200010];
int tr[200010*30][2],cnt=1,maxn[200010*30],minn[200010*30];
const int root=1;
void add(int nw,int val,int dep,int id)
{
	if (dep<0) return;
	maxn[nw]=max(id,maxn[nw]);
	minn[nw]=min(id,minn[nw]);
	int p=(val>>dep)&1;
	if (!tr[nw][p]) tr[nw][p]=++cnt,maxn[cnt]=minn[cnt]=id;
	add(tr[nw][p],val,dep-1,id);
}
int find(int val,int nw,int dep)
{
	if (dep<0) return 0;
	int p=(val>>dep)&1;
	if (tr[nw][p]) return find(val,tr[nw][p],dep-1);
	else return find(val,tr[nw][p^1],dep-1)+(1<<dep);
}
long long dfs(int nw,int dep)
{
	int L=tr[nw][0],R=tr[nw][1];
	if (maxn[L]&&maxn[R])
	{
		int mn=1<<30;
		for (int i=minn[L];i<=maxn[L];i++)
			mn=min(mn,find(a[i],R,dep-1));
		return dfs(L,dep-1)+dfs(R,dep-1)+mn+(1<<dep);
	}
	if (maxn[L]) return dfs(L,dep-1);
	if (maxn[R]) return dfs(R,dep-1);
	return 0;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+1+n);minn[root]=1;maxn[root]=n;
	for (int i=1;i<=n;i++)
		add(root,a[i],29,i);
	printf("%lld",dfs(root,29));
	
}

牛客的题比这些稍微绕一些:

题意:

给你一棵树,每条边有变权w,每次可以删一条边或者加一条边,每次操作后图要满足如下性质:

1.图是联通图

2.对于图里的所有环,边权的异或值为0

n ≤ 1 e 5   ,   w < 2 30 n\leq 1e5\ ,\ w< 2^{30} n1e5 , w<230

Solution:

仔细观察可以发现,如果要连边,边权就是这两个点之间的路径异或和,这样我们记录dis[i]为i到根节点的路径的异或和,这样i和j之间连的边的权值为dis[i]^dis[j](LCA到根之间的权值会异或两次,正好抵消),这样问题就转化成了上面的问题了。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int size,n;
int sum[100010];
struct edge{
	int to,next,v;
}e[200010];
int head[100010];
int tr[200010*30][2],cnt=1,maxn[200010*30],minn[200010*30];
const int root=1;
void add(int nw,int val,int dep,int id)
{
	if (dep<0) return;
	maxn[nw]=max(id,maxn[nw]);
	minn[nw]=min(id,minn[nw]);
	int p=(val>>dep)&1;
	if (!tr[nw][p]) tr[nw][p]=++cnt,maxn[cnt]=minn[cnt]=id;
	add(tr[nw][p],val,dep-1,id);
}
int find(int val,int nw,int dep)
{
	if (dep<0) return 0;
	int p=(val>>dep)&1;
	if (tr[nw][p]) return find(val,tr[nw][p],dep-1);
	else return find(val,tr[nw][p^1],dep-1)+(1<<dep);
}
long long dfs(int nw,int dep)
{
	int L=tr[nw][0],R=tr[nw][1];
	if (maxn[L]&&maxn[R])
	{
		int mn=1<<30;
		for (int i=minn[L];i<=maxn[L];i++)
			mn=min(mn,find(sum[i],R,dep-1));
		return dfs(L,dep-1)+dfs(R,dep-1)+mn+(1<<dep);
	}
	if (maxn[L]) return dfs(L,dep-1);
	if (maxn[R]) return dfs(R,dep-1);
	return 0;
}
void add(int x,int y,int v)
{
	size++;e[size].to=y;e[size].next=head[x];e[size].v=v;head[x]=size;
}
void dfs1(int x,int fa)
{
	for (int i=head[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if (y!=fa)
			sum[y]=sum[x]^e[i].v,dfs1(y,x);
	}
}
int main()
{
	scanf("%d",&n);
	for (int x,y,v,i=1;i<n;i++)
		scanf("%d%d%d",&x,&y,&v),x++,y++,add(x,y,v),add(y,x,v);
	dfs1(1,0);
	sort(sum+1,sum+n+1);
	minn[root]=1;maxn[root]=n;
	for (int i=1;i<=n;i++)
		add(root,sum[i],29,i);
	printf("%lld",dfs(root,29));
}