整理的算法模板合集: ACM模板


目录

  • 朴素并查集
  • 维护size的并查集
  • 维护到祖宗节点距离的并查集
  • 路径压缩和按秩合并
  • 2.5 边带权
  • 带权并查集求二分图最小环
  • 2.6 扩展域
  • 2.7 集合中单个元素的删除

并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的 合并查询 问题。 它支持两种操作:

  • 查找(Find):确定某个元素处于哪个子集;
  • 合并(Union):将两个子集合并成一个集合。

朴素并查集

int p[N]; //存储每个点的祖宗节点// 返回x的祖宗节点
int find(int x)
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;// 合并a和b所在的两个集合:
p[find(a)] = find(b);

维护size的并查集

int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量// 返回x的祖宗节点
int find(int x)
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{p[i] = i;size[i] = 1;
}// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);

维护到祖宗节点距离的并查集

int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离// 返回x的祖宗节点
int find(int x)
{if (p[x] != x){int u = find(p[x]);d[x] += d[p[x]];p[x] = u;}return p[x];
}// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{p[i] = i;d[i] = 0;
}// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

路径压缩和按秩合并

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

2.5 边带权

使用情景:
  N个节点有M对关系(M条边),每对关系(每条边)都有一个权值w,可以表示距离或划分成多个集合时的集合编号,问题依然是判断是否有冲突或者有多少条边是假的(冲突)等。

思路:
  给N个节点虚拟一个公共的根节点,增加一个数组s[n]记录每个节点到虚拟根节点的距离,把x,y直接的权值w看为(x,y)的相对距离。

Union(x,y,w)时额外把x,y到虚拟根节点的距离(s值)的相对差值设置为w;Find(x)时,压缩路径的同时把当前s值加上之前父节点的s值,得到真实距离。

具体实现:

  1. 初始化  init()f[n]数组记录节点的父节点,s[n]数组记录节点到虚拟根节点的距离:  for(int i=0;i<n;i++) {  f[i]=i;  s[i]=0; }2.  Find(x)if(x==f[x])return x;int t  = f[x];f[x] = Find(f[x]);s[x] += s[t];// s[[x] %= mod;  若s[x]表示划分成mod个集合时的集合编号等情况时,则需要求余。return f[x];3. Union(x, y,w)int fx = Find(x), fy = Find(y);  //此时已经s[x]和s[y]都已经计算为真值。if(fx != fy) {f [fx] = fy;s [fx] = (s[x] - s[y] + w + mod) % mod;}

4. 解决问题的算法步骤
    初始化后,遍历m对关系:若x,y的父节点不同,则Union(x,y,w);否则,若x与y的差值为w,则说明正确,继续遍历,不为w时说明出现冲突。

当s[x]只是代表划分为mod个集合时的集合编号时,应该比较s[x]与s[y]的值是否相同,相同时说明出现冲突;不相同时说明之前已经解决了,正确可继续遍历。

拓展:加权并查集主要得赋予并理解s[x]值的意义,较难掌握且应用广泛

/*关押罪犯*/
const int gxs = 2;    //模2就只有0,1两个值,分别代表两个不同的集合
const int mod = 2;
int n, m;
int f[maxn], s[maxn];    //f记录父节点(前驱),s记录到虚拟root点的距离void init() {for (int i = 0; i < maxn; i++) f[i] = i, s[i] = 0;
}
//查找
int finds(int x) {if (x == f[x]) return x;int t = f[x];f[x] = finds(f[x]);s[x] += s[t];    //s[x]原来是与t的相对距离,现在是与root的相对距离s[x] %= gxs;    //s值求余后代表所属监狱(二选一)return f[x];
}//新建关系
void unions(int x, int y, int w) {int fx = finds(x), fy = finds(y);if (fx != fy) {f[fy] = fx;s[fy] = s[x] - s[y] + w + gxs;    //相对距离设置为w,解决这一对冲突s[fy] %= mod;        //求余直接赋予实际意义:所属的mod个集合的编号}
}struct node {int a, b;ll val;bool operator < (const node &a)const {return val > a.val;}
};vector<node> que;int main() {cin >> n >> m;int a, b;ll v;for (int i = 0; i < m; i++) {cin >> a >> b >> v;que.push_back(node{ a,b,v });}sort(que.begin(), que.end());    //从大到小排序init();for (int i = 0; i < m; i++) {a = que[i].a;b = que[i].b;v = que[i].val;if (finds(a) == finds(b)) {    //在同一个集合就不能直接解决冲突if (s[a] == s[b]) {            //若s值相同就说明已经在同一个集合,冲突无法解决cout << v << endl;        //因为从大到小遍历,第一个解决不了的关系的val就是答案:最小化的最大冲突值return 0;}                            //否则说明解决之前的冲突后,当前冲突也被解决。}else {        //不在一个集合就可以通过设置s值解决冲突unions(a, b, 1);}}cout << 0 << endl;return 0;
}
/*d[x]表示x到根的距离*/
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]];//因为是按原顺序,所以我要加上我前面的长度,这才是真正的距离//d[x]^=d[f[x]];前缀异或和return f[x] = root;
}void Merge(int x,int y){int x = Get(x),y = Get(y);f[x] = y;d[x] = Size[y];//这里的x已经是原x的根了所以肯定是等于Size[y] += Size[x];
}

带权并查集求二分图最小环

有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 TiT_iTi​的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?

#include<cstdio>
#include<iostream>
using namespace std;
int f[200002],d[200002],n,minn,last;   //f保存祖先节点,d保存到其祖先节点的路径长。
int fa(int x)
{if (f[x]!=x)                       //查找时沿途更新祖先节点和路径长。 {int last=f[x];                 //记录父节点(会在递归中被更新)。 f[x]=fa(f[x]);                 //更新祖先节点。 d[x]+=d[last];                 //更新路径长(原来连在父节点上)。 }return f[x];
}
void check(int a,int b)
{int x=fa(a),y=fa(b);               //查找祖先节点。 if (x!=y) {f[x]=y; d[a]=d[b]+1;}   //若不相连,则连接两点,更新父节点和路径长。 else minn=min(minn,d[a]+d[b]+1);   //若已连接,则更新最小环长度。 return;
}
int main()
{int i,t;scanf("%d",&n);for (i=1;i<=n;i++) f[i]=i;         //祖先节点初始化为自己,路径长为0。 minn=0x7777777;for (i=1;i<=n;i++){scanf("%d",&t);check(i,t);                    //检查当前两点是否已有边相连接。 }printf("%d",minn);return 0;
}

2.6 扩展域

使用情景:

n个点有m对关系,把n个节点放入两个集合里,要求每对存在关系的两个节点不能放在同一个集合。问能否成功完成?

思路:

把每个节点扩展为两个节点(一正一反),若a与b不能在一起(在同一个集合),则把a的正节点与b的反节点放一起,把b的正节点与a的反节点放一起,这样就解决了a与b的冲突。若发现a的正节点与b的正节点已经在一起,那么说明之前的某对关系与(a,b)这对关系冲突,不可能同时满足,即不能成功完成整个操作。

具体实现:

1. 初始化 init()n个点,每个点扩展为两个点(一正一反),则需要一个容量为2*n的数组f[n],值全部初始化为自己即可:for(int i=0;i<2*n;i++) f[i]=i;(注意初始编号,若编号为[1,n],则初始化应该为:for(int i=1;i<=2*n;i++) f[i]=i;)一个点x的正点编号为x,反点编号为x+n(这样每个点的反点都是+n,规范、可读性强、不重复、易于理解)。2. Find(x)和Union(x, y)不需要修改,含义和实现不变。
  1. 解决问题的算法步骤

1)初始化2*n个节点的初始父节点,即它本身。

2)遍历m对关系,对每对(a,b),先找到a和b的父节点,若相等则说明(a,b)的关系与之前的关系有冲突,不能同时解决,则得到结果:不能完成整个操作。

否则执行:Union(a, b+n), Union(b, a+n).  (这时已经Find过了,直接执行f [fx] = fy这一句就等效与Union(x, y) )

3)若m对关系都成功解决,则得到结果:能够完成整个操作。

拓展:

由于扩展域会直接使数组容量翻倍,所有一般只解决这种“二分”问题,只扩展为2倍即可。

优点在于:结构简单,并查集的操作也不需要做改变,非常易于理解。  缺点显然就是:需要额外存储空间。

/*关押罪犯*/
struct edge {int a, b, c;
}e[maxm];bool cmp(edge a, edge b) {return a.c > b.c;
}int f[2 * maxn];
int Find(int x) {if (x == f[x])return x;return f[x] = Find(f[x]);
}int main() {int n, m;scanf("%d%d", &n, &m);for (int i = 0; i < m; i++)scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);sort(e, e + m, cmp);    //按仇恨值从大到小排序for (int i = 1; i <= 2 * n; i++)f[i] = i;    //初始化并查集int i;    //从大到小依次把每对罪犯安排到不同监狱for (i = 0; i < m; i++) {int a = Find(e[i].a), b = Find(e[i].b);if (a == b)break;    //两人的正点已在同一个集合,无法解决,最大冲突出现f[a] = Find(e[i].b + n);    //把a和b的反点(敌人)合并f[b] = Find(e[i].a + n);    //把b和a的反点(敌人)合并(每个点都有一个正点和反点)}if (i == m)printf("0");else printf("%d", e[i].c);return 0;
}

上述内容(边带权、拓展域)来源

2.7 集合中单个元素的删除

nnn个集合,第 iii 个集合里为 {i}\{i \}{i} 现在有 mmm 个操作。

  • 1xy1\ x\ y1 x y 把 xxx 所在集合合并到 yyy 所在集合中去
  • 2xy2\ x\ y2 x y把xxx元素移动到yyy集合中去。
  • 3x3\ x3 x输出 xxx 元素所在集合中的元素个数和总和。
/*给每一个点建一个虚拟点,所有的点都连虚拟点。例如i和i',以i为根的子结点都连i',这样进行操作2时将i连到j就是将i连到j',互不影响*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;const int N = 500007;int fa[N];
int n, m;
int cnt[N], sum[N];int find(int x){if(fa[x] == x)return x;return fa[x] = find(fa[x]);
}int main(){while(scanf("%d%d", &n, &m) != EOF){for(int i = 1;i <= n; ++ i)fa[i] = i + n;//直接初始化成它的虚拟结点for(int i = n + 1; i <= 2 * n; ++ i)fa[i] = i, cnt[i] = 1, sum[i] = i - n;//这里虚拟结点千万别忘了初始化为自己int x, y, op;while(m -- ){scanf("%d", &op);if(op == 1){scanf("%d%d", &x, &y);int fx = find(x);int fy = find(y);if(fx != fy){fa[fx] = fy;cnt[fy] += cnt[fx];sum[fy] += sum[fx];cnt[fx] = 0;sum[fx] = 0;}}else if(op == 2){scanf("%d%d", &x, &y);int fx = find(x);int fy = find(y);if(fx != fy){cnt[fy] ++ ;sum[fy] += x;cnt[fx] -- ;sum[fx] -= x;fa[x] = fy;}}else {scanf("%d", &x);int fx = find(x);printf("%d %d\n", cnt[fx], sum[fx]);}}}return 0;
}

并查集(边带权,拓展域)相关推荐

  1. POJ 1182 食物链 [并查集 带权并查集 开拓思路]

    传送门 P - 食物链 Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit  ...

  2. 树形结构 —— 并查集 —— 带权并查集

    [概述] 定义:带权并查集即是结点存有权值信息的并查集. 适用:当两个元素之间的关系可以量化,并且关系可以合并时,可以使用带权并查集来维护元素之间的关系. 权值:带权并查集每个元素的权通常描述其与并查 ...

  3. 数据结构进阶之并查集

    1.初探并查集 例题1:AcWing 237.程序自动分析 这题的思路其实比较好想,只要先考虑所有等于的情况并合并,然后再看不等于的情况,看是否出现矛盾即可.但是这题显然是需要离散化的,但是离散化也很 ...

  4. 并查集算法总结专题训练

    并查集算法总结&专题训练 1.概述 2.模板 3.例题 1.入门题: 2.与别的算法结合: 3.考思维的题: 4.二维转一维: 5.扩展域并查集&边带权并查集: 4.总结 1.概述 并 ...

  5. 【原创】并查集之扩展域与边带权

    [前言] 并查集是一种可以动态维护若干个不重叠的集合,并支持合并于查询的数据结构. 并查集的基本概念很简单,但是这样一种思想的用途十分广泛. 个人理解:这是一种很巧妙的,可以很好的处理对象之间关系的数 ...

  6. 【POJ - 1703】Find them, Catch them(带权并查集之--种类并查集 权为与父节点关系)

    题干: Find them, Catch them Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 36176   Accep ...

  7. P2024 [NOI2001]食物链[扩展域并查集]

    题目来源:洛谷 题目描述 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形.A 吃 B,B 吃 C,C 吃 A. 现有 N 个动物,以 1 - N 编号.每个动物都是 A,B,C ...

  8. 我知道并查集的可爱之处

    请开始您的并查集之旅 迈入门槛 文字概念 故事配图辅助理解 浅尝辄止 畅通工程 程序自动分析 supermarket 慢慢深入 银河英雄传说 parity game 1.带权并查集 2.扩展域并查集 ...

  9. ACM入门之【并查集】

    并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的 合并 及 查询 问题. 它支持两种操作: 查找:确定某个元素处于哪个子集. 合并:将两个子集合并成一个集合. 基本模板: const in ...

  10. 并查集一般高级应用的理解

    并查集高级应用一般是带权并查集,首先肯定要维护每个元素之间相应的关系,一个简单的数组要实现这个功能,则要将数组开大,将数组分为多个段,使每一段数组 代表不同的含义,而对于段与段之间的联系,就实现了对并 ...

最新文章

  1. Linux CPU数量判断命令
  2. python扫描端口脚本_Python实现的端口扫描功能示例
  3. python元编程_Python 元编程
  4. python 多窗口编辑
  5. Python稀疏矩阵运算库scipy.sparse用法精要
  6. Android基础 淡入淡出、上下弹出动画的
  7. ios assign、copy 、retain
  8. 游戏服务器当中的唯一名设计方法
  9. php 按行读取并分割字符串
  10. centos安装输入法
  11. SQL2008安装 VS2008安装(VS2010存在的情况下)
  12. 60款mac超酷炫动态苹果免费屏保壁纸
  13. Office系列软件之间不兼容以及office修复
  14. 3.17新政: 北京楼市重磅炸弹
  15. 使用wxml2canvas将微信小程序页面转为图片
  16. java七段数码管_05 七段数码管的实现
  17. 用RJS写的检测用户名和email是否存在
  18. Rimworld Mod制作教程1 认识Mod结构
  19. CNN做时间序列预测_深度学习与时间序列预测
  20. java设计模式_UML类图(上)

热门文章

  1. 深度学习七个实用技巧
  2. win10+centos7+Anaconda3+python+pytorch
  3. 06 回归算法 - 损失函数、过拟合欠拟合
  4. 收购Deis之后,微软首次动作发布了Draft
  5. Java - 网络编程(NetWork)
  6. Ambari Server网口带宽占用率很高问题的分析和解决办法
  7. ubuntu 搜狗输入法成功安装
  8. 查看linux机器是32位还是64位的方法
  9. 去Tech Ed得计划好
  10. idea ssm框架 mysql_idea搭建简单ssm框架的最详细教程(新)