CF888G 题解

前置知识 - Borůvka 算法

该算法用于求解最小生成树。

定义一次 “Borůvka 操作” 如下:

枚举所有不在连通块内的边。若一条边连接两个不同的连通块 \(x,y\),且这条边是连接 \(x,y\) 的边中权值最小的,那么就用这条边连接 \(x,y\),把 \(x,y\) 合并成一个连通块。

重复上述操直到所有点都在同一个连通块内为止。这样我们就得到了一棵最小生成树。

注意到我们每次使用 \(O(m)\) 的代价使一个规模为 \((n,m)\) 的问题变为了一个规模为 \((\frac{n}{2},m)\) 的问题。所以时间复杂度为 \(O(m\log n)\)

对于存在相同边权的图。比较两条边时需要多加一个关键字,通常为边的编号,这是为了防止两个连通块互相连的时候出现环。

给个代码(模板题):

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
#define fst first
#define scd second
typedef long long ll;
const int N=5005;
const int M=2e5+5;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x,T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
int n,m,u[M],v[M],w[M],best[N];
int fa[N],siz[N];
bool used[M];
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
inline bool merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y) return 0;
	if(siz[x]<siz[y]) swap(x,y);
	fa[y]=x,siz[x]+=siz[y];
	return 1;
}
inline bool better(int x,int y){
	if(!y) return 1;
	if(w[x]!=w[y]) return w[x]<w[y];
	return x<y; 
}
inline void Boruvka(){
	for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1;
	int cnt_edge=0; ll sum=0;
	bool flag=1;
	while(flag){
		flag=0;
		for(int i=1;i<=n;++i) best[i]=0;
		for(int i=1;i<=m;++i){
			if(used[i]) continue;
			int x=find(u[i]),y=find(v[i]);
			if(x==y) continue;
			if(better(i,best[x])) best[x]=i;
			if(better(i,best[y])) best[y]=i;
		}
		for(int i=1;i<=n;++i){
			if(best[i]&&!used[best[i]]){
				flag=1,cnt_edge++;
				sum+=w[best[i]];
				used[best[i]]=1;
				merge(u[best[i]],v[best[i]]);
			}
		}
	}
	if(cnt_edge!=n-1) printf("orz\n");
	else println(sum);
}
signed main(){
	read(n,m);
	for(int i=1;i<=m;++i) read(u[i],v[i],w[i]);
	Boruvka();
	return 0;
}

正题

看到异或,想到 01trie。

首先对 \(a\) 数组去重,显然只考虑这些点构成的图的 MST 与原图是等价的。

对全局建一棵 01trie,再对每一个点都建一棵 trie,动态开点。

由于我们肯定不能把边连出来,所以要稍微修改一下 Borůvka 算法的流程:

  • 修改 \(best\) 的定义:\(best_i\) 表示 \(i\) 这个点连到其他联通块最短边的另一个端点。并定义 \(mn_i\) 表示这条边的边权。初始为 \(+\infty\)

  • 枚举每个点 \(i\),用全局的 trie 减去 \(i\) 的 trie,然后在上面找到和另一个点 \(j\) 的最小异或和以及 \(j\) 的编号。如果 \(i,j\) 不在同一连通块里,用这条边更新 \(best\)\(mn\)

  • 再次枚举所有点,如果 \(mn_i\not=+\infty\)\(i\)\(best_i\) 不在同一连通块内(这是防止合并两次),生成树大小加上 \(mn_i\),并在并查集 & 01trie 中合并 \((i,best_i)\)(类似线段树合并)。

时间复杂度 \(O(n\log n\log V)\),空间复杂度 \(O(n\log V)\)

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
#define fst first
#define scd second
typedef long long ll;
const int N=2e5+5;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x,T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
int n,a[N];
namespace Trie{
	int tot,ch[N*50][2],rt[N*50],siz[N*50],tail[N*50];
	void insert(int &p,int dep,int pos,int w){
		if(!p) p=++tot;
		if(dep<0) return tail[p]=pos,siz[p]=1,void();
		int bit=(w>>dep)&1;
		insert(ch[p][bit],dep-1,pos,w);;
		siz[p]++;
	}
	void merge(int &L,int R){
		if(!L||!R) return L=L+R,void();
		merge(ch[L][0],ch[R][0]);
		merge(ch[L][1],ch[R][1]);
		siz[L]=siz[ch[L][0]]+siz[ch[L][1]];
		tail[L]=tail[R];
	}
	inline pii query(int pre,int p,int x){
		int ans=0;
		for(int i=30;i>=0;--i){
			int bit=(x>>i)&1;
			if(ch[p][bit]&&(siz[ch[p][bit]]-siz[ch[pre][bit]]>0)) p=ch[p][bit],pre=ch[pre][bit];
			else ans+=(1<<i),p=ch[p][!bit],pre=ch[pre][!bit];
		}
		return {ans,tail[p]};
	}	
}
int mn[N],best[N];
namespace DSU{
	int fa[N],siz[N];
	inline void init(){
		for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1;
	}
	int find(int x){
		if(x!=fa[x]) fa[x]=find(fa[x]);
		return fa[x];
	}
	inline bool merge(int x,int y){
		x=find(x),y=find(y);
		if(x==y) return 0;
		if(siz[x]<siz[y]) swap(x,y);
		fa[y]=x,siz[x]+=siz[y];
		Trie::merge(Trie::rt[x],Trie::rt[y]);
		return 1;
	}	
}
inline void Boruvka(){
	DSU::init();
	ll sum=0;
	bool flag=1;
	while(flag){
		flag=0;
		memset(mn,0x3f,sizeof(mn));
		for(int i=1;i<=n;++i){
			int x=DSU::find(i);
			pii res=Trie::query(Trie::rt[x],Trie::rt[0],a[i]);
			int y=DSU::find(res.scd),w=res.fst;
			if(x==y) continue;
			if(w<mn[x]) mn[x]=w,best[x]=y;
			if(w<mn[y]) mn[y]=w,best[y]=x;
		}
		for(int i=1;i<=n;++i){
			if(mn[i]!=0x3f3f3f3f&&DSU::find(i)!=DSU::find(best[i])){
				flag=1,sum+=mn[i];
				DSU::merge(i,best[i]);
			}
		}
	}
	println(sum);
}
signed main(){
	read(n);
	for(int i=1;i<=n;++i) read(a[i]);
	sort(a+1,a+n+1);
	n=unique(a+1,a+n+1)-(a+1);
	for(int i=1;i<=n;++i){
		Trie::insert(Trie::rt[0],30,i,a[i]);
		Trie::insert(Trie::rt[i],30,i,a[i]);
	}
	Boruvka();
	return 0;
}
posted @ 2024-02-28 15:12  Southern_Dynasty  阅读(23)  评论(0编辑  收藏  举报