目录

  • 一、路径压缩与按秩合并
  • 1.AcWing 237. 程序自动分析(NOIP2015)
  • 二、边带权并查集
  • 1.AcWing 238. 银河英雄传说(边带权并查集模板)
  • 2.AcWing 239. 奇偶游戏(边带权并查集+离散化+前缀和)
  • 三、扩展域并查集
  • 1.AcWing 239. 奇偶游戏
  • 2.P1525 关押罪犯(扩展域并查集)
    • 1.并查集
    • 注意:并查集必须先初始化!
    • 2.二分图
  • 3.AcWing 240. 食物链

声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。


下方链接为学习笔记目录链接(中转站)

学习笔记目录链接


ACM-ICPC在线模板


一、路径压缩与按秩合并

当我们在寻找祖先时,一旦元素多且来,并查集就会退化成单次O(n)O(n)O(n)的算法,为了解决这一问题我们可以在寻找祖先的过程中直接将子节点连在祖先上,这样可以大大降低复杂度,均摊复杂度是O(log(n))O(log(n))O(log(n))的

按秩合并也是常见的优化方法,“秩”的定义很广泛,举个例子,在不路径压缩的情况下,常见的情况是把子树的深度定义为秩

无论如何定义通常情况是把“秩”储存在根节点,合并的过程中把秩小的根节点插到根大的根节点上,这样可以减少操作的次数

特别的,如果把秩定义为集合的大小,那么采用了按秩合并的并查集又称“启发式并查集”

按秩合并的均摊复杂度是O(log(n))O(log(n))O(log(n))的,如果同时采用按秩合并和路径压缩均摊复杂度是O(α(n)),α(n)O(α(n)),α(n)O(α(n)),α(n)是反阿克曼函数

∀n≤21019729,α(n)≤5∀ n ≤2^{10^{19729}},α(n)≤5∀n≤21019729,α(n)≤5可以视为均摊复杂度为O(1)O(1)O(1)

不过通常情况下我们只需要采用路径压缩就够了

int fa[N];void init(){for(int i = 1;i <= n;++i)fa[i] = i;memset(Rank,0, sizeof Rank);
}int getfa(int x){//查询if(fa[x] == x) return x;return fa[x] = getfa(fa[x]);
}inline void union( int x, int y){//合并int fx = getfa(x) , fy = get(y);fa[fx] = fy;return ;
}inline bool same(int x , int y){ //判读是否在同一结合return getfa(x) == getfa(y) ;
}//把深度当作秩的 按秩合并
inline void rank_union( int x, int y)
{fx = getfa(x) , fy = getfa(y);if(Rank[fx] < Rank[fy]) fa[fx] = fy;else {fa[fy] = fx;if(Rank[fx] == Rank[fy]) Rank[fx]++;}return ;
}

并查集能在一张无向图中维护结点之间的连通性,实际上,并查集擅长动态维护许多具有传递性的关系。

1.AcWing 237. 程序自动分析(NOIP2015)

并查集的模板题,注意该题的数据达到1e9,我们可以离散化。
注意多组数据并查集中的fa数组也要清空!

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>using namespace std;const int N = 1000007;struct node{int x, y, z;bool operator<(const node &t)const {return z > t.z;}
}a[N];
int fa[N];
int n, m;
int b[N << 2];int find(int x){if(x == fa[x])return x;return fa[x] = find(fa[x]);
}void merge(int x, int y){x = find(x), y = find(y);fa[x] = y;
}int main(){int t;scanf("%d", &t);while(t -- ){scanf("%d", &n);memset(b, 0, sizeof b);memset(a, 0, sizeof a);memset(fa, 0, sizeof fa);bool flag = 0;int cnt = 0;for(int i = 1; i <= n; ++ i){scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);b[cnt ++ ] = a[i].x;b[cnt ++ ] = a[i].y;}sort(b, b + cnt);int res = unique(b, b + cnt) - b;for(int i = 1;i <= n; ++ i){a[i].x = lower_bound(b, b + res, a[i].x) - b;a[i].y = lower_bound(b, b + res, a[i].y) - b;}for(int i = 1; i <= res; ++ i)fa[i] = i;sort(a + 1, a + 1 + n);for(int i = 1; i <= n; ++ i){int x = a[i].x, y = a[i].y;if(a[i].z){merge(x, y);}else {if(find(x) == find(y)){puts("NO");flag = 1;break;}}}if(!flag)puts("YES");}return 0;
}

二、边带权并查集

并查集实际上是由若干棵树构成的森林,我们可以在树中的每一条边上记录权值,即维护一个数组ddd,用d[x]d[x]d[x]保存x到父结点f[x]f[x]f[x]的边权,在每次路径压缩后,每个访问过的结点都会直接指向树根,我们可以在路径压缩的同时统计每个结点到树根之间的路径上的一些信息,这就是“边带权”并查集

1.AcWing 238. 银河英雄传说(边带权并查集模板)

原题也太长了吧,神TM讲故事的吧
边带权并查集其实写起来非常简单,就是多统计两个数组而已。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<stack>#define ls (p<<1)
#define rs (p<<1|1)
//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;const int N = 1e5+7;int n,m;
int a[N];
int T;
int f[N];
int Size[N],d[N];
void init(){for(int i = 1;i<=N;++i)f[i] = i,Size[i] = 1;
}int Get(int x){if(x == f[x])return x;int root = Get(f[x]);//往上走一层d[x] += d[f[x]];//因为是按原顺序,所以我要加上我前面的长度,这才是真正的距离return f[x] = root;
}void Merge(int x,int y){int xx = Get(x),yy = Get(y);f[xx] = yy;d[xx] = Size[yy];//这里的xx是x的根所以肯定是等于Size[yy] += Size[xx];
}int main()
{ios::sync_with_stdio(false);cin.tie(0);cin>>T;init();over(i,1,T){int x,y;char ch;cin>>ch>>x>>y;if(ch == 'M'){Merge(x,y);}else {if(Get(x) == Get(y)){printf("%d\n",abs(d[y] - d[x])-1);//去掉自己}else puts("-1");}}return 0;
}

用scanf在AcWing上AC,但是在洛谷上全部RE,细思极恐

2.AcWing 239. 奇偶游戏(边带权并查集+离散化+前缀和)


我们可以用sum数组表示序列S的前缀和,那么会得到以下性质.

s[l~r]s[l~r]s[l~r]有偶数个1,等价于sum[l−1]与sum[r]sum[l-1]与sum[r]sum[l−1]与sum[r]奇偶性相同 (1+0=10+0=0(1+0=1\ 0+0=0(1+0=1 0+0=0,1是奇数,0是偶数)
s[l~r]s[l~r]s[l~r]有奇数个1,等价于sum[l−1]与sum[r]sum[l-1]与sum[r]sum[l−1]与sum[r]奇偶性不同 (1+1=00+1=0(1+1=0\ 0+1=0(1+1=0 0+1=0,1是奇数,0是偶数)
因为奇加奇等于偶,偶加奇等于奇,就是异或里的1 ^ 1 = 0,1 ^ 0 = 1,所以用前缀异或和来处理 。
其中前缀异或和数组 d[N],d[i]表示i到根节点的路径上的异或和(0为偶1为奇)
三种情况:
如果说x1和x2奇偶性质相同,x2与x3奇偶性质相同,那么x1和x3也相同
如果说x1和x2奇偶性质相同,x2与x3奇偶性质不同,那么x1和x3也不同
如果说x1和x2奇偶性质不同,x2与x3奇偶性质不同,那么x1和x3就相同

再有就是本题的数据特别大,n为1e9,数组不可能存得下,所以需要离散化,这里n特别大,但是m却很小,所以我们把l - 1和r缩小到1~2M,(因为这里的区间实际上区间里面的所有的数都没有用到,区间大小很大和为1没有区别,本题只是要求的各各区间的关系,相当于m个点而已,所以可以离散化)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<stack>#define ls (p<<1)
#define rs (p<<1|1)
//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;const int N = 2e4+7;int tot;
struct node{int l,r,ans;}query[N];
int n,m;
int a[N];
int f[N];
int d[N];//d[i]表示i到根节点的路径上的异或和(0为偶1为奇)void read_discrete(){//离散化cin>>n>>m;over(i,1,m){char str[5];scanf("%d%d%s",&query[i].l,&query[i].r,str);query[i].ans = (str[0] == 'o'?1:0);a[++tot] = query[i].l-1;a[++tot] = query[i].r;}sort(a+1,a+1+tot);n = unique(a+1,a+1+tot) -a-1;
}void init(){for(int i = 1;i <= n;++i)f[i] = i;
}int Get(int x){if(x == f[x])return x;int root = Get(f[x]);d[x]^=d[f[x]];return f[x] = root;
}int main()
{read_discrete();init();for(int i = 1;i <= m;++i){int x = lower_bound(a+1,a+1+n,(query[i].l-1)) - a ;int y = lower_bound(a+1,a+1+n,(query[i].r)) - a;int p = Get(x);int q = Get(y);if(p == q){//如果在同一个集合if((d[x]^d[y]) != query[i].ans){printf("%d\n",i - 1);return 0;}}else {f[p] = q;d[p] = d[x]^d[y]^query[i].ans;}}printf("%d\n",m);return 0;
}

三、扩展域并查集

并查集擅长的是动态维护图中具有传递性的关系。有的时候,我们需要传递的关系比较单一,但有的时候,传递的关系会比较复杂。这时候就需要用到并查集的扩展域。扩展域并查集可以维护多组关系,其主要思想是将一个点拆分成好几个点来维护多组关系。

1.AcWing 239. 奇偶游戏

对,还是我QWQ

2.P1525 关押罪犯(扩展域并查集)

洛谷题目链接


输入

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出

3512

1.并查集

有意思的一道并查集的题,需要一些思维。
用并查集来维护,当a和b并到一起的时候说明他们两个在同一个监狱之中。
本题要求最大的仇恨值最小,所以用结构体存数据,先排序,仇恨值最大的排在前面,遍历这个结构体数组,遵循把敌人的敌人和我放在一个监狱的原则来add即可。其中要注意如果可以都不在一个监狱不发生冲突就输出0,所以循环要从1到m+1,这样到m+1的时候,数据:0,0,0,直接会check输出 0

注意:并查集必须先初始化!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+7;
const ll mod=2147483647;
ll n,m,w,b,c;
ll f[N],vis[N];
struct node
{ll x,y,z;bool operator<(const node &s)const{return z>s.z;}
}a[N];
inline void init()//并查集必须要初始化!
{for(int i=1;i<=n;++i)f[i]=i;
}
inline ll finds(ll x)
{return f[x]==x?x:f[x]=finds(f[x]);
}
inline void add(ll x,ll y)
{x=finds(x);y=finds(y);f[x]=y;
}
inline bool check(ll x,ll y)
{x=finds(x);y=finds(y);return x==y;
}
int main()
{scanf("%lld %lld",&n,&m);init();for(int i=1;i<=m;++i){scanf("%lld %lld %lld",&a[i].x,&a[i].y,&a[i].z);}sort(a+1,a+1+m);for(int i=1;i<=m+1;++i){if(check(a[i].x,a[i].y)){printf("%lld\n",a[i].z);break;}if(!vis[a[i].x])vis[a[i].x]=a[i].y;else add(vis[a[i].x],a[i].y);if(!vis[a[i].y])vis[a[i].y]=a[i].x;else add(vis[a[i].y],a[i].x);}return 0;
}

2.二分图

这道题总共有两个监狱,把一群人分到两个监狱里很明显就是一个二分图
那么就可以直接二分答案并用二分图判断即可
别人的代码和详解:
二分答案+二分图判断
有任何疑问欢迎评论哦虽然我真的很菜

3.AcWing 240. 食物链


半年前的代码

#include<cstdio>
int fa[300005];
int n,k,ans;
/*inline int read()
{int sum=0;char ch=getchar();while(ch>'9'||ch<'0')ch=getchar();while(ch<='9'&&ch>='0')sum=sum*10+ch-48,ch=getchar();return sum;
}*/
int find(int x)
{if(x!=fa[x]) fa[x]=find(fa[x]);return fa[x];
}
int join(int x,int y)
{int r1=find(fa[x]),r2=find(fa[y]);fa[r1]=r2;
}
int main()
{int a,b,c;scanf("%d%d",&n,&k);for(int i=1;i<=3*n;++i)fa[i]=i;for(int i=1;i<=k;++i){scanf("%d%d%d",&a,&b,&c);if(c>n||b>n){ans++;continue;}if(a==1){if(find(b+n)==find(c)||find(b+2*n)==find(c)){ans++;continue;}join(b,c);join(b+n,c+n);join(b+2*n,c+2*n);}else if(a==2){if(b==c){ans++;continue;}if(find(b)==find(c)||find(b+2*n)==find(c)){ans++;continue;}join(b,c+2*n);join(b+n,c);join(b+2*n,c+n);}}printf("%d\n",ans);return 0;
}

0x41.数据结构进阶 - 并查集相关推荐

  1. 0x41 数据结构进阶-并查集

    A题 程序自动分析 题目链接:https://ac.nowcoder.com/acm/contest/1031/A 题目描述 在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足. 考 ...

  2. 数据结构-PHP 并查集(Union Find)

    文章目录 数据结构-PHP 并查集(Union Find) 1.并查集示意图 2.并查集合并 3.并查集简单的代码示例 3.1 PHP代码定义 3.2 输出演示 数据结构-PHP 并查集(Union ...

  3. 数据结构 之 并查集

    并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(Disjoint Sets)的合并及查询问题. 有一个联合-查找算法(union-find algorithm)定义了两个操作用于此数据结构 ...

  4. 数据结构之并查集Union-Find Sets

    1.  概述 并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题. 2.  基本操作 并查集 ...

  5. 数据结构之并查集:并查集的介绍与Python代码实现——18

    并查集的介绍 并查集(Union-find)数据结构也称作合并查找集(Merge-find set)或者不相交集数据结构(disjoint-set data structure),它是一种记录了由一个 ...

  6. 数据结构 之 并查集(Disjoint Set)

    一.并查集的概念:     首先,为了引出并查集,先介绍几个概念:     1.等价关系(Equivalent Relation)     自反性.对称性.传递性.     假设a和b存在等价关系.记 ...

  7. 数据结构 7并查集(DISJOINT SET)

    并查集(The disjoint set ADT) 等价关系 Relation R:若对于每一对元素(a,b),a,b∈S,aRb或者为true或者为false,则称集合S上定义关系R.如果aRb为t ...

  8. [重修数据结构0x03]并查集、堆、优先队列(2021.8.11)

    前言 在做遍历的题目的时候,发现掌握一些特殊的数据结构和技巧有时对解决题目有着决定性的作用,不可不学.因此特地拿出来两天学习一下并查集.堆.优先队列.以后有更多思考和感悟再加补充吧.内容来自算法笔记, ...

  9. 高阶数据结构(1):并查集 与 图

    "Head in the clouds" 一.并查集 (1)认识并查集? 在一些问题中需要将n个不同的元素划分成 一些不想交的集合. 开始时,每个元素自成一个单元素集合,然后按一定 ...

最新文章

  1. MySQL关于事务控制、视图、存储过程和函数
  2. Python工程师面试题集合
  3. eclipse下创建Maven项目
  4. 外观模式(三层解耦)
  5. java类对象转化成字符串_String类型字符串(xml格式)转换成java对象类型
  6. jQuery 生成随机字符
  7. 基于PHP+MySQL的物流配送管理系统平台
  8. DOSBox安装流程
  9. 几个不知道算不算经典的游戏
  10. emmx文件用什么软件打开电脑_我告诉你emmx文件怎么打开
  11. Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)
  12. 【2018年的最佳固态硬盘】最好的固态硬盘可以增强你的电脑
  13. 微信开发者工具登录时tunneling socket could not be established
  14. 从B树、B+树、B*树谈到R 树
  15. 一起白piao网页学知识吧
  16. 字节面试:谈谈索引为什么能提高查询性能?
  17. 转发微雪课堂的STM32CubeMX系列教程
  18. SBW(线控转向系统Matlab/Simulink模型搭建)
  19. 20140916阿里巴巴面试经历---成都站
  20. rap2使用姿势——前端、后端、测试必看(多gif图)

热门文章

  1. 程序运行慢?你怕是写的假 Python
  2. 【OpenCV 4开发详解】两图像间的像素操作
  3. 8个必备的PHP功能开发
  4. VIEW层AJAX提交表单到Controller的实体(AJAX传递序列化的输入元素)
  5. Acronis移动方案(四)
  6. Linux之网络管理(2)虚拟网卡
  7. Java中Queue和BlockingQueue的区别
  8. IOS 笔试题(二)
  9. jfreechart的使用
  10. EntityFramework+DomainDataSource+Silverlight完成数据读取分页排序与修改