【总结】带权并查集

原理

p [ x ] p[x] p[x]表示 x x x f a [ x ] fa[x] fa[x]的路径权值,有下式:
s [ x ] = p [ x ] + s [ f a [ x ] ] s[x]=p[x]+s[fa[x]] s[x]=p[x]+s[fa[x]]
可以在路径压缩时完成这一点,这类题的关键是求出两个根节点 u , v u,v u,v的关系(即求 s [ u ] s[u] s[u],默认 v v v为新的根节点)

A. 箱子

有n个箱子,初始时每个箱子单独为一列;

接下来有p行输入,M, x, y 或者 C, x;

对于M,x,y:表示将x箱子所在的一列箱子搬到y所在的一列箱子上;

对于C,x:表示求箱子x下面有多少个箱子;

分析:

  1. 求深度一定是累加,跑一次后就链接到根节点了(路径压缩的特性,本身路径长度会改变,只是点的权值不会变),根节点权值为0,故不会影响结果。如果累加式出现了常数,多次调用时一定会越加越大。
  2. 这道题可以直接根节点相连,虽然路径并非直接连在一起,但将路径累加在权值上即可
  3. 权值可以记录一些路径压缩下改变的东西,这就是它的用处
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 3e4 + 5;
int n, x, y, fa[MAXN], s[MAXN], d[MAXN];  //设底部为根节点
char c;
void makeset() {
    
    
    for (int i = 1; i <= 30000; i++) {
    
    
        fa[i] = i;
        s[i] = 1;
        d[i] = 0;
    }
}
int findset(int x) {
    
    
    if (fa[x] != x) {
    
    
        int t = fa[x];
        fa[x] = findset(fa[x]);
        d[x] += d[t];  //关键 (表示到父节点的距离,+1会错 )
    }
    return fa[x];
}
void unionset(int x, int y) {
    
    
    int u = findset(x), v = findset(y);
    if (u == v)
        return;
    else {
    
    
        fa[u] = v;     //父节点直接设置成根节点
        d[u] += s[v];  //关键(加距离)
        s[v] += s[u];  //关键(加树的总深度)
    }
}
int main() {
    
    
    scanf("%d", &n);
    makeset();
    for (int i = 1; i <= n; i++) {
    
    
        cin >> c;
        if (c == 'M') {
    
    
            scanf("%d%d", &x, &y);
            unionset(x, y);
        } else {
    
    
            scanf("%d", &x);
            int u = findset(x);
            printf("%d\n", d[x]);
        }
    }
}

B.食物链

分析:用带权并查集
x到根节点root的路径权值s[x]就表示了x与root的关系。若x,y在同一集合中,则s[x]和s[y]的差就是x,y的关系。
可以用0,1,2表示不同的种类,由于构成一个环,所以可以模3解决。
另外:s[x,y]=s[x,z]+s[z,y],显然可以用路径压缩。

对于x,y的根节点u,v,要合并u,v两颗树,只需要:
f a [ u ] = v ; fa[u]=v; fa[u]=v;
s [ u ] = ( t y p d − 1 − s [ x ] + s [ y ] + 3 ) m o d 3 ; s[u]=(typd-1-s[x]+s[y]+3) mod 3; s[u]=(typd1s[x]+s[y]+3)mod3;
式二可以用向量证明,不过感性理解即可。
注意并查集只能合并根节点,x,y,是不能动的。其实把u接到y后面也没错。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=100005;

void read(int &x) {
    
    
	int f=1;x=0;char c=getchar();
	while(c<'0'||c>'9') {
    
    if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {
    
    x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}

int n,q,fa[maxn],s[maxn],tot;
int find(int x) {
    
    
	if(fa[x]!=x) {
    
    
		int t=fa[x];
		fa[x]=find(fa[x]);
		s[x]=(s[x]+s[t])%3;
	}
	return fa[x];
}
void unionset(int x,int y) {
    
    
	int u=find(x),v=find(y);
	if(u==v) return;
	fa[u]=v;
}
int main() {
    
    
	read(n),read(q);
	for(int i=1;i<=n;i++) fa[i]=i,s[i]=0;
	for(int i=1;i<=q;i++) {
    
    
		int x,y,typd;
		read(typd),read(x),read(y);
		if(x>n||y>n||(typd==2&&x==y)) {
    
    
			tot++;
			continue;
		}
		int u=find(x),v=find(y);
		if(u==v&&(s[x]-s[y]+3)%3!=typd-1) tot++;
		else if(u!=v) {
    
    
			fa[u]=v;
			s[u]=(typd-1-s[x]+s[y]+3)%3;
		}
	}
	printf("%d",tot);
}

C.有多少答案是错误的

题目描述
现在有n个数(1~n)和m次询问,每次询问给出一个区间[a,b]以及这个区间内的价值总和,现在请你找出给出的m次查询中,有多少个询问是和前面不相互矛盾的已存在的询问相互矛盾。

输入格式
第一行包含两个整数n,m(1<=n<=200000, 1<=m<=40000),表示数据的范围以及查询的次数。

接下来每行3个数字a,b,s,表示区间[a,b]内的价值总和为s。其中0<a<=b<=n。

题目保证所有数据的s的和在int范围内。

ps:因为老师们只是讲了思路,所以我来说明一下具体怎么实现

分析:

首先,本题要明确:一个区间的和可能是负的,所以只有根据前面题目中给出的点找矛盾,如:

1 10 10
4 6 50
这个样例是正确的

本题的处理方法和上题一样:
f a [ u ] = v ; fa[u]=v; fa[u]=v;
s u m [ u ] = s − s u m [ x ] + s u m [ y ] ; sum[u]=s-sum[x]+sum[y]; sum[u]=ssum[x]+sum[y];

#include<cstdio>
using namespace std;
const int MAXN=200005;
int fa[MAXN],n,m,ans,sum[MAXN];
void makeset() {
    
    
	for(int i=1;i<=n;i++) {
    
    
		fa[i]=i;
		sum[i]=0;
	}
}
int find(int x) {
    
    
	if(fa[x]!=x) {
    
    
		int f=fa[x];
		fa[x]=find(fa[x]);
		sum[x]+=sum[f];
	}
	return fa[x];
}
void unionset(int x,int y,int s) {
    
    
	int u=find(x),v=find(y);
	if(u==v) {
    
    
		if(sum[y]-sum[x]!=s) ans++;
		return;
	}
	fa[v]=u,sum[v]=sum[x]+s-sum[y];
}
int main() {
    
    
	scanf("%d%d",&n,&m);
	makeset();
	for(int i=1;i<=m;i++) {
    
    
		int x,y,s;
		scanf("%d%d%d",&x,&y,&s);
		unionset(x-1,y,s);
	}
	printf("%d",ans);
}

总结:这三道题展现了并查集问题的相似性,它不但可以维护关系,还可以维护数值,实现集合内任意两点间路径的查询。

猜你喜欢

转载自blog.csdn.net/cqbzlydd/article/details/105494903