就是我洛谷博客的简单搬运
关于并查集
$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个空洞,满足上可通天,下可入地。
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;
}
}
那时的码风和现在不太一样…
已经尽量统一
加一个二分答案枚举工作半径即可,自行实现
维护连通性+
刚刚讲的都是图上连通性,其实可以类比到数列上,意义是 f a [ x ] fa[x] fa[x]指向下一个可操作对象。
倒序枚举,然后并查集跳跃染色即可,并维护并查集关系。
//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]);
}
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
n−1条边的连通图,有向无向的都有,有根无根的都有。
树有什么特殊的性质呢?
对于任意两个树上的点,他们之间的简单路径只有一条。简单路径指没有重复的边的连接两点的最短路径。
树上无环。
唉?我告诉你,某著名算法
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;
}
从最小生成树到最小生成树计数
这道题目可以用高端算法解决,但数据经过特殊构造,苦心孤诣地让暴力通过
引理:对于一种权值的边,在所有最小生成树中的数目相同
不证!不证!不证!
所以我们就可以随便求出来一种,然后暴力检验。
暴力检验就是,暴力搜出一种权值的边的排列,检验是否能组成最小生成树。
对了,不能路径压缩,因为会丢信息,不信你做
#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);
}
小结:
并查集中用到的一些思想,在其他算法中也会有所体现,可以细细体悟
并查集算法本身也会经常作为重要工具被使用。
辅助阅读材料: