带权并查集详解:

首先讨论一下带权并查集的主要功能。并查集主要是维护不同元素之间传递关系的数据结构,如果不带权,那么维护的就是不同元素是否属于同一个集合,即连通关系。如果带权,那么除了维护连通性之外,还会维护同一个集合中各个元素之间的关系。

对于带权并查集的具体实现,最重要的维护元素是 d[x]d[x],表示元素 xx 到根节点 rootroot 的偏移量,即 d[x]=xrootd[x] = x \rightarrow root,而这个偏移量是由我们自己定义的,取决于具体题意。

接下来我们通过一道例题,HDU 3038HDU\ 3038 来仔细讲解这个数据结构。


题意:

一个长度为 nn 个序列,一共 mm 个询问。每次询问给出 l,r,suml,r,sum,表示这个序列中区间 [l,r][l,r]sumsum 和。 问一共有几组询问与前面为真的询问不符,如果该组询问不符合事实,忽略这组询问。


思路:

很明显,这题主要维护序列上每个节点之间的连通关系,以及节点之间的偏移量。我们定义 d[x]=xroot=sum[root]sum[x]d[x] = x\rightarrow root = sum[root]-sum[x],即区间 [x+1,root][x+1, root] 的和。这里有一个需要注意的地方,我们不能令 d[x]=xroot=[x,root]d[x] = x\rightarrow root = [x, root] 区间和,因为 d[x]d[x] 初始化时为 00,而如果是 [x,root][x,root] 的区间和,那么初始化时 d[x]=a[x]=[x,x]d[x] = a[x] = [x,x] 区间和,而不是00,因此会发生错误。所以我们维护的是 d[x]=xroot=sum[root]sum[x]d[x] = x\rightarrow root = sum[root]-sum[x]

然后我们再来考虑并查集的路径压缩操作。并查集的本质是一颗森林,带权并查集增加的操作就是增加了边权。

现在我们来考虑下面的这个图,执行 find(B)find(B) 的过程,fa[B]=find(fa[B])fa[B] = find(fa[B]),就可以将 BB 连向 rootroot,但是我们需要更新 d[B]d[B]d[B]=Broot=BA + Arootd[B] = B\rightarrow root=B\rightarrow A\ +\ A\rightarrow root,而在路径压缩之前,d[B]=BAd[B] = B\rightarrow A因此路径压缩时,d[B]=d[B]+d[A]d[B] = d[B]+d[A],也可以理解为边权相加。

int find(int x){
	if(fa[x]==x) return x;
	int root = find(fa[x]);
	d[x] += d[fa[x]], fa[x] = root;
	return fa[x];
}

【带权并查集详解】以HDU 3038为例【How Many Answers Are Wrong】
现在我们开始做这道题目。首先初始化,fa[i]=i, d[i]=0fa[i] = i, \ d[i] = 0,然后对于每个 [a,b]=sumsum[b]sum[a1]=sum[a,b] = sum,sum[b]-sum[a-1] = sum,我们先判断 a1a-1bb 是否在同一个集合内。

如果不在同一个集合内,则说明 a1a-1bb 没有偏移关系,因此我们需要将这两个集合进行合并,存入他们的偏移关系,令fa=find(a1)fb=find(b)fa = find(a-1),fb = find(b)(a1)b=(a1)fa + fafb + fbb=sum=d[a1]+d[fa]d[b](a-1)\rightarrow b = (a-1)\rightarrow fa\ +\ fa\rightarrow fb \ + \ fb\rightarrow b = sum = d[a-1]+d[fa]-d[b],而此处的 d[fa]d[fa] 就是合并之后,fafa 对于 fbfb 的偏移量。

如果在同一个集合中,则判断 (a1)b=(a1)root + rootb=d[a1]d[b](a-1)\rightarrow b = (a-1)\rightarrow root\ + \ root \rightarrow b=d[a-1]-d[b] 是否等于 sumsum,如果不等于,则为假。

到此,这题就可以解决了。最后回顾一下带权并查集,带权并查集的核心就是维护多个元素之间的连通以及偏移关系,甚至可以维护多个偏移关系,而偏移的东西可以理解为当前节点到根节点的边长之和。


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 2*1e5+1000;

int fa[N],d[N],n,m;

int find(int x){
	if(fa[x]==x) return x;
	int root = find(fa[x]);
	d[x] += d[fa[x]], fa[x] = root;
	return fa[x];
}

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		rep(i,0,n) fa[i] = i, d[i] = 0;
		int ct = 0;
		rep(i,1,m){
			int a,b,c; scanf("%d%d%d",&a,&b,&c); a--;
			int f1 = find(a), f2 = find(b);
			if(f1 == f2){
				if(d[a]-d[b] != c) ct++;
			}
			else{
				fa[f1] = f2, d[f1] = c+d[b]-d[a];
			}
		}
		printf("%d\n",ct);
	}
	return 0;
}

相关文章: