大佬的讲解:简洁而优美的结构 - 并查集 | 一文吃透 “带权并查集” 不同应用场景 | “手撕” 蓝桥杯A组J题 - 推导部分和 - 掘金 (juejin.cn)
路径压缩后的朴素并查集:
初始化:
fa[i]:i的根节点
rank[i]:以i为根节点的子树深度
初始化:
void init(int n)
{
for(int i=0;i<n;i++)
{
rank[i]=1;
fa[i]=i;
}
}
查询:查询两个元素是否在同一个集合
int find(int x)
{ if(fa[x]==x)return x;//本身就是根节点
return fa[x]=find(fa[x]); //使当前节点在本次查找根节点后,直接记录根节点,//避免重复计算
}
合并:两个不相交的集合合并成一个集合
void merge(int i,int j)
{
int x=find(i),y=find(j);
if(rank[x]<=rank[y])
fa[x]=fa[y];
else
fa[y]=fa[x];
if(x!=y&&rank[x]==rank[y])
{rank[y]++;}
}
//不用改变rank,原因:两个集合存在一个rank大的集合,另外一个集合合并到该集合中后成为子树,大集合rank值未发生变化
带权并查集:

带权并查集:分为 size并查集和dist并查集
size并查集:表示并查集所含的结点数
dist并查集:表示从当前点到父节点的距离
size并查集:一般用于计算连通块中点的数量的题目
p[]表示每个点的祖宗节点
size[]只有祖宗节点的size有意义,表示祖宗节点所在的集合中点的数量
查询:
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]); //路径压缩,p[x]直接记录祖宗是谁
return p[x];
}
合并:
bool merge(int x,int y)
{
int X=find(x);
int Y==find(y);
if(X==Y) return false;
size[x]+=size[y];//不是用一个祖宗,合并成同一个集合,并更新size值
p[y]=x;//没有判断谁的集合更大,直接归为一个集合
}
dist并查集:用于食物链的内容
初始化
int p[N],dist[N]//dist记录到祖宗的距离
for(int i=1;i<=n;i++)
{
p[i]=i;
dist[i]=0;
}
并查集查询操作:
查询的过程中跟新路径
int find(int x)
{
if(x!=p[x])
{
int root=find(p[x]);
dist[x]+=dist[p[x]];
p[x]=root;
}
return p[x];
}
合并操作
boolmerge(int x,int y)
{
int X=find(x);
int Y=find(y);
if(X==Y) return false;
p[x]=y;
dist[x]+=(dist[y]+1);//加上另外一个集合的总个数
return true;
}
为什么y集合合并到x集合,只需要更新y集合的根节点距离新集合根节点的距离,而不需要将y集合内的所有节点距离新根节点的距离全部更新呢?
这是因为:只要后续操作中,需要对原y集合内的某个节点进行查询操作的时候,查询操作便会对此点所在路径上的所有点全部进行数据的更新,即相当于原y集合内的节点的p[x]和_dist[x]都会更新为指向合并后新的根节点和距离新根节点的距离
蓝桥杯:推导部分和
import java.util.*;
public class Main {
static int n,m,q;
static long dist[];
static int fa[];
public static void main(String args[])
{
Scanner in=new Scanner(System.in);
n=in.nextInt();
m=in.nextInt();
q=in.nextInt();
fa=new int[n+1];
dist=new long[n+1];
init(fa,dist);
while(m!=0)
{
int l,r;
long s;
l=in.nextInt();
r=in.nextInt();
s=in.nextLong();
merge(l-1,r,s);
m--;
}
while(q!=0)
{
int l,r;
l=in.nextInt();
r=in.nextInt();
if(find(l-1)!=find(r)) System.out.println("UNKNOWN");
else System.out.println(dist[l-1]-dist[r]);
q--;
}
}
public static int find(int x)
{
if(x==fa[x]) return x;
int root=find(fa[x]);
dist[x]+=dist[fa[x]];
fa[x]=root;
return fa[x];
}
public static void init(int fa[],long dist[])
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
dist[i]=0;
}
}
public static boolean merge(int x,int y,long value)
{
int u=find(x);
int v=find(y);
if(u==v) return false;
dist[u]=dist[y]+value-dist[x];//dist[i]表示i节点到祖宗节点的距离
//value-dist[x]表示 原来节点到新要合并节点的距离-原来节点到原来根节点的距离,即自己集合的根节点到新集合根节点的距离
//dist[y]表示合并到新集合的合并点到该集合跟节点的距离
fa[u]=v;
return true;
}
}