【总结】带权并查集

原理

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); }

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


__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530431.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(18)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示