并查集--从模板到应用拓展

就是我洛谷博客的简单搬运

关于并查集

$Part $ 1 引入

经典故事引入

话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的帮派,通过两两之间的朋友关系串联起来。而不在同一个帮派的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物。这样,每个圈子就可以这样命名“中国同胞队”美国同胞队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样,想打一架得先问个几十年,饿都饿死了,受不了。这样一来,队长面子上也挂不住了,不仅效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否是一个帮派的,至于他们是如何通过朋友关系相关联的,以及每个圈子内部的结构是怎样的,甚至队长是谁,都不重要了。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。

int pre[1000]; 这个数组,记录了每个大侠的上级是谁。大侠们从1或者0开始编号(依据题意而定),pre[15]=3就表示15号大侠的上级是3号大侠。如果一个人的上级就是他自己,那说明他就是掌门人了,查找到此为止。也有孤家寡人自成一派的,比如欧阳锋,那么他的上级就是他自己。每个人都只认自己的上级。比如胡青牛同学只知道自己的上级是杨左使。张无忌是谁?不认识!要想知道自己的掌门是谁,只能一级级查上去。

设想这样一个场景:两个互不相识的大侠碰面了,想知道能不能干一场。 于是赶紧打电话问自己的上级:“你是不是掌门?” 上级说:“我不是呀,我的上级是谁谁谁,你问问他看看。” 一路问下去,原来两人的最终boss都是东厂曹公公。 “哎呀呀,原来是自己人,有礼有礼,在下三营六组白面葫芦娃!” “幸会幸会,在下九营十八组仙子狗尾巴花!” 两人高高兴兴地手拉手喝酒去了。 “等等等等,两位大侠请留步,还有事情没完成呢!”我叫住他俩。 “哦,对了,还要做路径压缩。”两人醒悟。 白面葫芦娃打电话给他的上级六组长:“组长啊,我查过了,其实偶们的掌门是曹公公。不如偶们一起结拜在曹公公手下吧,省得级别太低,以后查找掌门麻烦。” “唔,有道理。” 白面葫芦娃接着打电话给刚才拜访过的三营长……仙子狗尾巴花也做了同样的事情。 这样,查询中所有涉及到的人物都聚集在曹公公的直接领导下。每次查询都做了优化处理,所以整个门派树的层数都会维持在比较低的水平上。路径压缩的代码,看得懂很好,看不懂可以自己模拟一下,很简单的一个递归而已。总之它所实现的功能就是这么个意思。

引用自

P a r t Part Part 2 基本实现

这个还是挺简单的,如果你 P a r t Part Part 1的故事懂了的话

并查集能够高效的维护不重叠的集合的关系,可以查找两个数是否在同一集合,并支持合并两个集合

一般的,我们用 f a [ x ] fa[x] fa[x]表示 f a fa fa的父亲( x x x f a [ x ] fa[x] fa[x]所在的集合里),当查找 x x x位于哪个集合时,不断递归查找 f a [ x ] fa[x] fa[x]位于的集合,直到当前 x x x满足 f a [ x ] = x fa[x]=x fa[x]=x,返回 x x x

对于一个 x x x, x x x f a [ x ] fa[x] fa[x]一定在一个集合,但注意 f a [ x ] fa[x] fa[x]不一定能找到与 x x x的关系,即不能找儿子。而且注意, f a [ x ] fa[x] fa[x]并不一定是集合的代言人

合并 x , y x,y x,y的话,就分别找到 x , y x,y x,y的父亲,然后把 f a [ y ] fa[y] fa[y]设为 x x x就可以了。

记得初始化,即把 f a [ x ] fa[x] fa[x]设为 x x x

int fa[];
······
inline void find(int x,int y)  { fa[y]=x; }
inline int union(int x) { return x==fa[x]?x:fa[x]; }//注意下方的建议
······
for(reg int i=1;i<=n;++i) fa[i]=i;//预处理为自己
······
//查找x的父亲
int res/*result*/=查找函数(x);
//x,y是否在同一集合
find(x)==find(y);
//合并x,y所在集合
union(find(x),find(y))

起名的话,提供以下建议:

  • f i n d , u n i o n find,union find,union前面加上自己的名字或想加的东西。为什么?试试把union打到dev里看看
  • 或者直接用 f i n d , m e r g e find,merge find,merge,应该没有锅的,但我不喜欢 m e r g e merge merge这个词 q w q qwq qwq

查找复杂度 O ( n ) O(n) O(n)

什么?太屑?没事,还有优化

作业:

亲戚(这个应该能过

T i p s Tips Tips: 可能需要一点 S T L STL STL基础( m a p map map)

  • 唉?为什么 P a r t Part Part 1的故事 P a r t Part Part 2没对应全啊
  • 因为写的人傻

P a r t Part Part 3: 优化

主要讲路径压缩和按秩合并

路径压缩

路径压缩很简单的,好理解,就是每次查询后,都把 f a [ x ] fa[x] fa[x]设为唯一正版代言人,就好了。

具体实现,就是在查询 x x x所在集合时,若 x x x不是所在集合的代言人,就把递归查找 f a [ x ] fa[x] fa[x]设为 x x x的父亲

f i n d find find均摊复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)

查询 4 4 4所在集合,返回 1 1 1,并把从 4 4 4到根的路径上的点的 f a fa fa设为 1 1 1

代码:

inline int szfind(int x) { return x==fa[x]?x:fa[x]=szfind(fa[x]); }

没变多少吧,实在不理解就背板子嘛

一般的题目中使用路径压缩就足够了。

按秩合并

按秩合并不太常用,在单纯的并查集题目中没有必要打。

但是,在无法使用路径压缩的情况下,即使用路径压缩会导致信息丢失,常见于需要支持撤销的题目中,还可以用按秩合并优化。

按秩合并的“秩”,指集合的“高度”(或者是集合的大小)。

而按秩合并,就是把秩小的集合合并到秩大的集合中,即把小的集合的代表的父亲设为大的集合的代表。

合并 5 , 2 5,2 5,2所在集合, 5 5 5所在集合代表为 5 , 2 5,2 5,2所在集合代表为 1 , 1 1,1 1,1集合秩大于 5 5 5集合,把5集合并入 1 1 1中, 5 , 1 5,1 5,1连边

inline void szunion(int x,int y) { fa[y]=x,dep[x]=mymax(dep[x],dep[y]+1); }
······
int x0=szfind(x),y0=szfind(y);
if(x0!=y0) {
    if(dep[x0]<dep[y0]) swap(x0,y0);
    szunion(x0,y0);
}

f i n d find find均摊复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)

综合使用两种优化方法,均摊复杂度为 O ( α ( n ) ) O(α(n)) O(α(n)),近似为常数,但没必要

模板

并查集

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define chk_digit(c) (c>='0'&&c<='9')
inline int read(){
	reg int x=0,f=1;reg char c=getchar();
	while(!chk_digit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(chk_digit(c)) { x=x*10+c-'0',c=getchar(); }
	return x*f;
}
int n,m,fa[10002];
inline int szfind(int x) { return x==fa[x]?x:fa[x]=szfind(fa[x]); }
#define szunion(x,y) fa[y]=x
int main(){
	n=read(),m=read();
	for(reg int i=1;i<=n;++i) fa[i]=i;
	for(reg int i=1;i<=m;++i){
		int chk=read(),x=read(),y=read();
		if(chk==1) { szunion(szfind(x),szfind(y)); }
		else { szfind(x)==szfind(y)?cout<<"Y\n":cout<<"N\n"; }
	}
}

习题

家谱

修复公路

P a r t Part Part 4 基本应用:连通性

维护连通性

并查集可以用来维护是否在同一个集合,也可以用来表两点连通

例题1

搜索做法不再讲述。

并查集做法就是,对能够相互到达的空洞连边维护连通性,并检查能否有1个空洞,满足上可通天,下可入地

h 1 [ x ] , h 2 [ x ] h1[x],h2[x] h1[x],h2[x]表示该空洞能到达的最高,最低海拔

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n,h,r,fa[1005],h1[1005],h2[1005],flag;
inline int szfind(int x) { return x==fa[x]?x:fa[x]=szfind(fa[x]); }
inline void szunion(int x,int y) { fa[y]=x; }
struct node{ int x,y,z; }a[1005];
main() {
    scanf("%lld",&t);
    while(t--) {
        scanf("%lld%lld%lld",&n,&h,&r);
        for(int i=1;i<=n;++i) {
            int x,y,z;
            scanf("%lld%lld%lld",&x,&y,&z),a[i].x=x,a[i].y=y,a[i].z=z,h1[i]=z+r;h2[i]=z-r;fa[i]=i;
            if(h1[i]>=h&&h2[i]<=0) flag=1;
        }
        if(flag){ cout<<"Yes\n";flag=0;continue; }
        for(int i=1;i<=n;++i) {
            for(int j=i+1;j<=n;++j) {
                int x=a[i].x-a[j].x,y=a[i].y-a[j].y,z=a[i].z-a[j].z,x0=szfind(i),y0=szfind(j);
                if(x0==y0) continue;
                if(x*x+y*y+z*z<=4*r*r) szunion(x0,y0);
            }
        }
        for(int i=1;i<=n;++i) {
            h1[fa[i]]=max(h1[fa[i]],h1[i]),h2[fa[i]]=min(h2[fa[i]],h2[i]);
            if(h1[fa[i]]>=h&&h2[fa[i]]<=0) { cout<<"Yes\n";flag=1;break; }
        }
        if(!flag){ cout<<"No\n"; }
        flag=0;
    }
}

那时的码风和现在不太一样…

已经尽量统一

类似题

作业1

加一个二分答案枚举工作半径即可,自行实现

作业2(贪心)

维护连通性+

刚刚讲的都是图上连通性,其实可以类比到数列上,意义是 f a [ x ] fa[x] fa[x]指向下一个可操作对象。

例题3

倒序枚举,然后并查集跳跃染色即可,并维护并查集关系。

//fa[i]表示,i前面第一个没染色的雪花
#include<bits/stdc++.h>
using namespace std;
#define reg register
inline int read(){
	reg char c;reg int f=1,x=0;
	while(!isdigit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(isdigit(c)) { x=x*10+c-'0';c=getchar(); }
	return x*f;
}
int n,m,p,q,fa[1000001],col[1000001];
inline int myfind(int x){ if(x==fa[x]) return x;return fa[x]=myfind(fa[x]); }
int main(){
	n=read(),m=read(),p=read(),q=read();
	for(reg int i=1;i<=n;++i) fa[i]=i;
	for(reg int i=m;i>=1;--i){//依照题意,倒序染色(颜色以最后一次染上的颜色为准)
		int l=(i*p+q)%n+1,r=(i*q+p)%n+1;
		if(l>r) swap(l,r);
		for(reg int j=r;j>=l;){
			int t=myfind(j);
			if(t==j){
				col[j]=i,fa[j]=myfind(j-1);
			}j=fa[j];
		}
	}
	for(reg int i=1;i<=n;++i) printf("%d\n",col[i]);
}

作业3

作业4(与贪心结合)

P a r t Part Part 5 从并查集到其它算法

T i p s Tips Tips :以下内容可能需要 l c a lca lca/倍增基础

最小生成树

树是个什么东西呢。

树是一个 n n n个点, n − 1 n-1 n1条边的连通图有向无向的都有,有根无根的都有

树有什么特殊的性质呢?

对于任意两个树上的点,他们之间的简单路径只有一条。简单路径指没有重复的边的连接两点的最短路径

树上无环。

唉?我告诉你,某著名算法 F l o y d Floyd Floyd,众所周知的 i , j , k i,j,k i,j,k错误竟然在树上是对的

而我们的最小生成树,指的就是从一个图中剖离出一个边权和最小的树。

当给出的图不连通时,自然无解。

k r u s c a l kruscal kruscal算法运用了贪心思想。

从小到大排序,贪心加边,如果两个点连通则下一条(不成环),否则加边,维护连通性。

这样,我们就不会加入浪费权值的无用长边,从而使总边权最小。

模板

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define chk_digit(c) (c>='0'&&c<='9')
inline int read(){
	reg int x=0,f=1;reg char c=getchar();
	while(!chk_digit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(chk_digit(c)) { x=x*10+c-'0',c=getchar(); }
	return x*f;
}
int n,m,sum,fa[5002],cnt,h[5002],k;
inline int szfind(int x) { return x==fa[x]?x:fa[x]=szfind(fa[x]); }
#define szunion(x,y) fa[y]=x
struct node{ int x,y,next,val; }edg[200002<<1];
inline bool cmp(node p,node q) { return p.val<q.val; }
int main(){
	n=read(),m=read();
	for(reg int i=1;i<=m;++i) edg[i].x=read(),edg[i].y=read(),edg[i].val=read();
	for(reg int i=1;i<=n;++i) fa[i]=i;
	sort(edg+1,edg+m+1,cmp);
	for(reg int i=1;i<=m;++i) {
		int x=edg[i].x,y=edg[i].y,x0=szfind(x),y0=szfind(y);
		if(x0!=y0) {
			++k,sum+=edg[i].val;
			szunion(x0,y0);
		}
	}
	if(k<n-1) { cout<<"orz\n"; }
	else cout<<sum<<endl;
}

作业题1

作业题2

从最小生成树到最小生成树计数

这道题目可以用高端算法解决,但数据经过特殊构造,苦心孤诣地让暴力通过

引理:对于一种权值的边,在所有最小生成树中的数目相同

不证!不证!不证!

所以我们就可以随便求出来一种,然后暴力检验。

暴力检验就是,暴力搜出一种权值的边的排列,检验是否能组成最小生成树。

对了,不能路径压缩,因为会丢信息,不信你做

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define p 31011
#define reg register
#define chk_digit(c) (c>='0'&&c<='9')
inline int read(){
	reg int x=0,f=1;reg char c=getchar();
	while(!chk_digit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(chk_digit(c)) { x=x*10+c-'0',c=getchar(); }
	return x*f;
}
int n,m,h[102],cnt,fa[102],tent,k,now_sum,ans=1;
struct node{ int x,y,val; }edg[1002<<1];
struct node1{ int l,r,num; }a[1002<<1];
inline void reset() { for(reg int i=1;i<=n;++i) fa[i]=i; }
inline bool cmp(node a,node b) { return a.val<b.val; }
#define szunion(x,y) fa[y]=x;
inline int szfind(int x) { return x==fa[x]?x:szfind(fa[x]); }
inline void dfs(int x,int now,int num){
	if(now==a[x].r+1) { if(num==a[x].num) now_sum=(now_sum+1)%p;return; }
	int x0=szfind(edg[now].x),y0=szfind(edg[now].y);
	if(x0!=y0) {
		szunion(x0,y0);
		dfs(x,now+1,num+1);
		fa[x0]=x0,fa[y0]=y0;
	}
	dfs(x,now+1,num);
}
signed main(){
	n=read(),m=read();reset();
	for(reg int i=1;i<=m;++i)
		edg[i].x=read(),edg[i].y=read(),edg[i].val=read();
	sort(edg+1,edg+m+1,cmp);
	for(reg int i=1;i<=m;++i) {
		int x0=szfind(edg[i].x),y0=szfind(edg[i].y);
		if(edg[i].val!=edg[i-1].val) a[tent].r=i-1,a[++tent].l=i;
		if(x0!=y0) { fa[y0]=x0,++a[tent].num,++k; }
	}a[tent].r=m;
	if(k<n-1) { printf("0\n");return 0; }
	reset();
	for(reg int i=1;i<=tent;++i) {
		now_sum=0,dfs(i,a[i].l,0);
		ans=(ans*now_sum)%p;
		for(reg int j=a[i].l;j<=a[i].r;++j){
			int x0=szfind(edg[j].x),y0=szfind(edg[j].y);
			if(x0!=y0) szunion(x0,y0);
		}
	}
	printf("%lld\n",ans);
}

从最小生成树到次小生成树

不做要求,难度较大,不讲了

其实也不难,就是求出最小生成树,倍增求最长边,然后加入不在最先生成树中的边,此时必然成环,只用当前边替换掉最长边即可。

但这道题是严格次小,要维护的细节较多。

总之思路简单,细节很多,谁写谁知道,所以还是不建议写。

代码还是可以放的嘛

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define int long long
#define inf (1ll<<62)
inline int read(){
	reg int x=0,f=1;reg char c=getchar();
	while(!isdigit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(isdigit(c)) { x=x*10+c-'0',c=getchar(); }
	return x*f;
}
int n,m,h[100002],dep[100002],cnte,fa[100002][19],f[100002],d[100002][19],d2[100002][19],cnt,vis[300002],minn,ans=1ll<<62;
inline int szfind(int x) { 
	if(x==f[x]) return x;
	return f[x]=szfind(f[x]);
}
inline void szunion(int x,int y) { f[y]=x; }
inline int mymax(int x,int y) { return x>=y?x:y; }
inline int mymin(int x,int y) { return x>=y?y:x; }
struct node{ int y,next,num,val; }edg[300002<<1];
struct edge{ int x,y,val,num; }e[300002];
inline void add(int x,int y,int val,int num) { edg[++cnt].next=h[x],edg[cnt].val=val,edg[cnt].y=y,h[x]=cnt,edg[cnt].num=num; }
inline void add1(int x,int y,int val,int num) { e[++cnte].x=x,e[cnte].y=y,e[cnte].val=val,e[cnte].num=num; }
inline bool cmp(edge x,edge y) { return x.val<y.val; }
inline void maketree(int x,int fat){
	for(reg int i=h[x];i;i=edg[i].next){
		int y=edg[i].y;if(y==fat||(!vis[edg[i].num])) continue;
		fa[y][0]=x,d[y][0]=edg[i].val,d2[y][0]=-inf,dep[y]=dep[x]+1,maketree(y,x);
	}
}
inline void bz(){
	for(reg int j=1;j<=18;++j){
		for(reg int i=1;i<=n;++i){
			fa[i][j]=fa[fa[i][j-1]][j-1],d[i][j]=mymax(d[i][j-1],d[fa[i][j-1]][j-1]);
			if(d[i][j-1]==d[fa[i][j-1]][j-1]) continue;
			d2[i][j]=mymax(d2[i][j],mymin(d[i][j-1],d[fa[i][j-1]][j-1]));
		}
	}
}
inline int get_max(int x,int y,int val){
	if(dep[x]<dep[y]) swap(x,y);
	int t=dep[x]-dep[y],ans=0;
	for(reg int i=0;i<=18;++i){
		if((1<<i)&t) {
			if(d[x][i]!=val) ans=mymax(ans,d[x][i]);
			else ans=mymax(ans,d2[x][i]);
			x=fa[x][i];
		}
	}
	if(x==y) return ans;
	for(reg int i=18;i>=0;--i){
		if(fa[x][i]!=fa[y][i]) {
			if(d[x][i]!=val) ans=mymax(ans,d[x][i]);
			else ans=mymax(ans,d2[x][i]);
			if(d[y][i]!=val) ans=mymax(ans,d[y][i]);
			else ans=mymax(ans,d2[y][i]);
			x=fa[x][i],y=fa[y][i];
		}
	}
	if(d[x][0]!=val) ans=mymax(ans,d[x][0]);else ans=mymax(ans,d2[x][0]);
	if(d[y][0]!=val) ans=mymax(ans,d[y][0]);else ans=mymax(ans,d2[y][0]);
	return ans;
}
main(){
	n=read(),m=read();
	for(reg int i=1;i<=m;++i){
		int x=read(),y=read(),z=read();
		add(x,y,z,i),add(y,x,z,i),add1(x,y,z,i);
	}
	for(reg int i=1;i<=n;++i) f[i]=i;
	sort(e+1,e+m+1,cmp);
	for(reg int i=1;i<=m;++i){
		int x=e[i].x,y=e[i].y,val=e[i].val,x0=szfind(x),y0=szfind(y);
		if(x0==y0) continue;
		vis[e[i].num]=1,szunion(x0,y0),minn+=val;
	}
	maketree(1,0);bz();
	for(reg int i=1;i<=m;++i){
		if(vis[e[i].num]) continue;
		int x=e[i].x,y=e[i].y,val=e[i].val;
		int tmp=get_max(x,y,val);
		ans=mymin(ans,minn-tmp+val);
	}
	printf("%lld\n",ans);
}

比较简单的有汉堡店

n 2 n^2 n2加边, n 2 n^2 n2枚举,找最优答案。

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define ll long long
#define chk_digit(c) (c>='0'&&c<='9')
inline ll read(){
	reg ll x=0,f=1;reg char c=getchar();
	while(!chk_digit(c)) { if(c=='-') f=-1;c=getchar(); }
	while(chk_digit(c)) { x=x*10+c-'0',c=getchar(); }
	return x*f;
}
#define get_pow(x) pow(x,2)
ll n,h[1002],cnt,k,father[1002],tent,dep[1002],fa[1002][11];
double sum,dis[1002][11],ans;
inline ll szfind(ll x) { return x==father[x]?x:father[x]=szfind(father[x]); } 
#define szunion(x,y) father[y]=x;
#define myswap(x,y) x^=y,y^=x,x^=y
struct con { ll x,y;double val; }e[1002*1002];
struct pt { ll x,y,val; }a[1002];
inline bool cmp(con p,con q) { return p.val<q.val; }
struct node{ ll y,next;double val; }edg[1002<<1];
inline void add(ll x,ll y,double val){
	edg[++cnt].y=y,edg[cnt].val=val,edg[cnt].next=h[x],h[x]=cnt;
	edg[++cnt].y=x,edg[cnt].val=val,edg[cnt].next=h[y],h[y]=cnt;
}
inline void maketree(ll x,ll fat){
	for(reg ll i=h[x];i;i=edg[i].next) {
		ll y=edg[i].y;if(y==fat) continue;
		dep[y]=dep[x]+1,fa[y][0]=x,dis[y][0]=edg[i].val;
		maketree(y,x);
	}
}
inline void bz(){
	for(reg ll j=1;j<=10;++j)
		for(reg ll i=1;i<=n;++i)
			fa[i][j]=fa[fa[i][j-1]][j-1],dis[i][j]=max(dis[fa[i][j-1]][j-1],dis[i][j-1]);
}
inline ll lca(ll x,ll y) {
	if(dep[x]<dep[y]) myswap(x,y);
	ll t=dep[x]-dep[y];
	for(reg ll i=0;i<=10;++i) if((1<<i)&t) x=fa[x][i];
	if(x==y) return x;
	for(reg ll i=10;i>=0;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
inline double get_dis(ll x,ll y){
	double ans=0;int t=dep[x]-dep[y];
	if(x==y) return ans;
	for(reg ll i=0;i<=10;++i) if((1<<i)&t) ans=max(ans,dis[x][i]),x=fa[x][i];
	return ans;
}
inline double get_ans(ll x,ll y) {
	ll anc=lca(x,y);
	double max_val=max(get_dis(x,anc),get_dis(y,anc));
	double ans=(a[x].val+a[y].val)/(sum-max_val);
	return ans;
}
int main(){
	n=read();
	for(reg ll i=1;i<=n;++i) father[i]=i;
	for(reg ll i=1;i<=n;++i) a[i].x=read(),a[i].y=read(),a[i].val=read();
	for(reg ll i=1;i<=n;++i) 
		for(reg ll j=i+1;j<=n;++j)
			e[++tent].x=i,e[tent].y=j,e[tent].val=sqrt(get_pow(a[i].x-a[j].x)+get_pow(a[i].y-a[j].y));
	sort(e+1,e+tent+1,cmp);
	for(reg ll i=1;i<=tent;++i) {
		ll x0=szfind(e[i].x),y0=szfind(e[i].y);
		if(x0==y0) continue;
		sum+=e[i].val,add(e[i].x,e[i].y,e[i].val),szunion(x0,y0);
	}
	maketree(1,0),bz();
	for(reg ll i=1;i<=n;++i)
		for(reg ll j=i+1;j<=n;++j)
			ans=max(ans,get_ans(i,j));
	printf("%.2lf\n",ans);
}

小结:

并查集中用到的一些思想,在其他算法中也会有所体现,可以细细体悟

并查集算法本身也会经常作为重要工具被使用。

辅助阅读材料:

并查集优化

k r u s c a l kruscal kruscal 重构树

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值