并查集简介

并查集的两类操作:

  1. Get 查询任意一个元素是属于哪一个集合。
  2. Merge 把两个集合合并在一起。

基本思想:找到代表元。

注意有两种方法:

  • 使用一个固定的值(查询方便,但是在合并的时候需要修改大量的值,比较复杂)

  • 使用树形结构,这样合并的时候可以直接让一个叫另一个

    eg. f[root1] = root2


并查集的路径压缩以及按秩合并

路径压缩:在每一次进行合并的时候,顺便更改每一个节点的值。(均摊复杂度:\(O(logN)\))

按秩合并:每一次查询的均摊复杂度是\(O(logN)\)。

如果两个一起使用,那么最终的复杂度是线性的。但是正常使用路径压缩就行。


使用并查集来维护具传递性的性质

仅仅维护具有传递性:

AcWing237. 程序自动分析



思路:

  • 一种方法是使用树的无向图来进行维护相等关系。(每一个块里面全部相等)
  • 再就是使用并查集来维护传递关系。
    • 注意:相等具有传递性,但是不相等不具备传递性。

代码:

#include <bits/stdc++.h>
using namespace std;
int fa[200010];
map<int , int >mp;
vector<pair<int, int> >v;
int get(int x)
{
if(fa[x] == x) return x;
return fa[x] = get(fa[x]);
}
void solve(int n)
{
bool tag = true;
v.clear();
int cnt = 0;
for(int i = 1; i <= 2*n; i++) fa[i] = i;
mp.clear();
int a, b, eq;
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d", &a, &b, &eq);
if(mp.find(a) == mp.end()) mp[a] = ++cnt;
if(mp.find(b) == mp.end()) mp[b] = ++cnt;
if(eq == 0)
{
v.push_back(make_pair(mp[a], mp[b]));
}
else
{
fa[get(mp[a])] = get(mp[b]);
}
}
for(vector<pair<int, int > >::iterator it = v.begin(); it != v.end(); it++)
{
pair<int, int>t = *it;
if(get(t.first) == get(t.second))
{
tag = false;
break;
}
}
//for(int i = 1; i <= 2*n; i++)
//{
// printf("%d\t%d", i, fa[])
//}
if(tag) puts("YES");
else puts("NO");
}
int main()
{
int T;
cin >> T;
while(T--)
{
int n;
scanf("%d", &n);
solve(n);
}
return 0;
}

并查集的带权路径以及扩展域:

  • 可以在logN的复杂度内查询某一个节点(在“链”中)到根节点的距离
  • 但是也具有要求

    在合并的时候,必须是把一个集合全部按照原有的顺序合并到另一个集合的末尾。

这个时候,有两个数组 d 和 size 集合:

  1. 如果是根节点,那么就size里存有这一个集合元素的多少。
  2. 其他节点存放到父亲节点的带权路径长度d。

注意:仅仅在查询过后,d数组的内容才是到根节点的距离。


AcWing238. 银河英雄传说

代码

#include <bits/stdc++.h>
using namespace std;
int fa[30010];
int d[30010];
int s[30010];
int get(int x)
{
if(x == fa[x]) return x;
int root = get(fa[x]);
d[x] += d[fa[x]];
fa[x] = root;
return root;
}
int merge(int a, int b)
{
int x = get(a);
int y = get(b);
fa[x] = y;
d[x] = s[y];
s[y] += s[x];
}
int main()
{
int T;
cin >> T;
for(int i = 0; i <= 30002; i++)
{
fa[i] = i;
d[i] = 0;
s[i] = 1;
}
while(T--)
{
char buf[12];
int a, b;
scanf("%s%d%d", buf, &a, &b);
if(buf[0]=='M')
{
if(get(a) != get(b))//如果在一次合并之后,再次合并,那么就是把一个空的合并到了战舰末尾。
merge(a, b);
}
else
{
int x = get(a);
int y = get(b);
if(x==y)
{
if (a==b) puts("0");//注意可能两次询问的是同一个战舰
else printf("%d\n", abs(d[a]-d[b])-1);
}
else
puts("-1");
}
}
return 0;
}

239. 奇偶游戏

思路:

这道题目涉及到区间内的操作。

需要把区间的操作转化为端点的操作。

不妨假设有一个前缀数组,保存着从最开始的点到这一个位置1的个数。

现在进行一下转化:

  • 如果一个区间内有奇数个1,那么端点的奇偶性不同。
  • 如果一个区间内有偶数个1,那么端点奇偶性相同。

维护一种传递的关系: 使用并查集

注意:区间长度大,但是总体数目少,可以考虑离散化。

方法一:采用带边权来进行实现。

与上一题不同,对于一个抽象的并查集,把一个合并到另一个上时,边权是可以自己给定的。

浅浅地证明一下:当区间端点没有发生冲突,那么存在一种序列满足条件

(为了说明区间端点的冲突是造成判断说谎的充分必要条件)

#include <bits/stdc++.h>
using namespace std;
map<int , int> mp;
int fa[10010];
int d[10010];
//int s[10010];
int get(int x)
{
if(x == fa[x]) return x;
int root = get(fa[x]);
d[x] = d[fa[x]] ^ d[x];
return fa[x] = root;
} bool merge(int a, int b, int mod)
{
int x = get(a);
int y = get(b);
if(x==y)
{
if(d[a] ^ d[b] == mod)
return true;
else
return false;
}
fa[x] = y;
d[x] = mod^d[a]^d[b];
return true;
}
int main()
{
for(int i = 0; i <= 10000; i++)
{
fa[i] = i;
d[i] = 0;
}
int ans = INT_MAX;
int cnt = 0;
int N, M;
cin >> N >> M;
for(int i = 1; i <= M; i++)
{
int a, b;
char buf[12];
scanf("%d%d%s", &a, &b, buf);
a--;
if(mp.find(a) == mp.end()) mp[a] = ++cnt;
if(mp.find(b) == mp.end()) mp[b] = ++cnt;
bool tag;
if(buf[0] == 'e')
tag = merge(mp[a], mp[b], 0);
else
tag = merge(mp[a], mp[b], 1);
if(!tag)
{
ans = min(ans, i);
}
}
if(ans == INT_MAX)
printf("%d\n", M);
else
printf("%d", ans-1);
return 0;
}

方法二:采用拓展域来进行求解:

还是有一种等价关系,只不过可能由这一个域推到另一个域上。所以,采用多个域来维护传递性。

思路

如果进行查询,没有发现矛盾,直接合并(因为如果本来在一起,合并也没有什么后果)

代码
#include <bits/stdc++.h>
using namespace std;
map<int , int> mp;
int fa[20010];
const int divv = 10002;
int get(int x)
{
if(x== fa[x]) return x;
return fa[x] = get(fa[x]);
}
bool merge(int x, int y, int mod)
{
int x_odd = x;
int x_even = x + divv;
int y_odd = y;
int y_even = y + divv;
if(mod)//奇数
{
if(get(x_odd) == get(y_odd))
{
return false;
}
else
{
fa[get(x_odd)] = get(y_even);
fa[get(y_odd)] = get(x_even);
}
}
else
{
if(get(x_odd) == get(y_even))
return false;
else
{
fa[get(x_odd)] = get(y_odd);
fa[get(x_even)] = get(y_even);
}
}
return true; }
int main()
{
for(int i = 0; i <= 20009; i++)
{
fa[i] = i;
}
int ans = INT_MAX;
int cnt = 0;
int N, M;
cin >> N >> M;
for(int i = 1; i <= M; i++)
{
int a, b;
char buf[12];
scanf("%d%d%s", &a, &b, buf);
a--;
if(mp.find(a) == mp.end()) mp[a] = ++cnt;
if(mp.find(b) == mp.end()) mp[b] = ++cnt;
bool tag;
if(buf[0] == 'e')
tag = merge(mp[a], mp[b], 0);
else
tag = merge(mp[a], mp[b], 1);
if(!tag)
{
ans = min(ans, i);
}
}
if(ans == INT_MAX)
printf("%d\n", M);
else
printf("%d", ans-1);
return 0;
}

AcWing240. 食物链



思路:这道题目是要动态地维护传递关系,所以考虑到并查集。

对于这个关系来说,看不出来传递性,所以要使用扩展域或者是边带权。

方法一:扩展域

#include <bits/stdc++.h>
using namespace std;
int fa[150012];
const int divv = 50000;
int get(int x)
{
if(x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
bool merge(int tag, int a, int b)
{
int a1 = a, a2 = a+divv, a3 = a2+divv;
int b1 = b, b2 = b+divv, b3 = b2 + divv;
if(tag==1)//表示a与b是同类
{
if(get(a1)==get(b2) || get(b1)==get(a2))
return false;
fa[get(a1)] = get(b1);
fa[get(a2)] = get(b2);
fa[get(a3)] = get(b3);
}
else//表示a吃b
{
if(get(a1)==get(b1) || get(b1) == get(a2))
return false;
fa[get(a1)] = get(b2);
fa[get(a2)] = get(b3);
fa[get(a3)] = get(b1);
}
return true;
}
int main()
{
for(int i = 1; i <= 150010; i++)
{
fa[i] = i;
}
int cnt = 0;
bool right = true;
int T, K;
cin >> T >> K;
for(int i = 1; i <= K; i++)
{ int tag, a, b;
scanf("%d%d%d", &tag, &a, &b);
if(a > T || b > T) //针对第二条判断真假
{
cnt ++;
right = false;
}
else if(!merge(tag, a, b))//注意:这个必须是else,因为如果
//上面一旦不合法,那么就不能进行下面的操作
{
cnt++;
right = false;
}
}
printf("%d\n", cnt);
return 0;
}

算法竞赛进阶指南0x41并查集的更多相关文章

  1. POJ1639 算法竞赛进阶指南 野餐规划

    题目描述 原题链接 一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界. 现在他们想要去公园玩耍,但是他们的经费非常紧缺. 他们将乘车前往公园,为了减少花费,他们决定选择一种合 ...

  2. 算法竞赛进阶指南 0x43 线段树

    目录 线段树简介 线段树的简单代码实现 建树代码 修改操作 查询操作 线段树的查询操作的时间复杂度分析: AcWing245. 你能回答这些问题吗 思路 代码[时间复杂度:\(O( \space(N+ ...

  3. 《算法竞赛进阶指南》0x10 基本数据结构 Hash

    Hash的基本知识 字符串hash算法将字符串看成p进制数字,再将结果mod q例如:abcabcdefg 将字母转换位数字(1231234567)=(1*p9+2*p8+3*p7+1*p6+2*p5 ...

  4. 《算法竞赛进阶指南》1.4Hash

    137. 雪花雪花雪花 有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,-,ai,6. 因为雪花的形状是封闭的环形,所以从任何一 ...

  5. bzoj 1787 && bzoj 1832: [Ahoi2008]Meet 紧急集合(倍增LCA)算法竞赛进阶指南

    题目描述 原题连接 Y岛风景美丽宜人,气候温和,物产丰富. Y岛上有N个城市(编号\(1,2,-,N\)),有\(N-1\)条城市间的道路连接着它们. 每一条道路都连接某两个城市. 幸运的是,小可可通 ...

  6. 算法竞赛进阶指南 0x00 基本算法

    放在原来这个地方不太方便,影响阅读体验.为了读者能更好的刷题,另起一篇随笔. 0x00 基本算法 0x01 位运算 [题目][64位整数乘法] 知识点:快速幂思想的灵活运用 [题目][最短Hamilt ...

  7. 算法竞赛进阶指南--快速幂,求a^b mod p

    // 快速幂,求a^b mod p int power(int a, int b, int p) { int ans = 1; for (; b; b >>= 1) { if (b &am ...

  8. 算法竞赛进阶指南0x14 Hash

    组成部分: 哈希函数: 链表 AcWing137. 雪花雪花雪花 因为所需要数据量过于大,所以只能以O(n)的复杂度. 所以不可能在实现的过程中一一顺时针逆时针进行比较,所以采用一种合适的数据结构. ...

  9. 《算法竞赛进阶指南》1.6Trie

    142. 前缀统计 给定N个字符串S1,S2-SN,接下来进行M次询问,每次询问给定一个字符串T,求S1-SN中有多少个字符串是T的前缀. 输入字符串的总长度不超过106,仅包含小写字母. 输入格式 ...

随机推荐

  1. 为什么不建议给MySQL设置Null值?《死磕MySQL系列 十八》

    大家好,我是咔咔 不期速成,日拱一卒 之前ElasticSearch系列文章中提到了如何处理空值,若为Null则会直接报错,因为在ElasticSearch中当字段值为null时.空数组.null值数 ...

  2. .NET混合开发解决方案9 WebView2控件的导航事件

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  3. ELK 1.4 logstash各种插件

      kibana各种插件: 1.过虑插件 kv (1)KV插件:接收一个键值数据,按照指定分隔符解析为Logstash 事件中的数据结构,放到事件顶层.  常用字段:    • field_split ...

  4. [codeforces] 暑期训练之打卡题(二)

    每个标题都做了题目原网址的超链接 Day11<Given Length and Sum of Digits...> 题意: 给定一个数 m 和 一个长度 s,计算最大和最小在 s 长度下, ...

  5. 『忘了再学』Shell基础 — 16、位置参数变量

    目录 1.位置参数变量$n 2.位置参数变量$*和$@ 3.位置参数变量$# 位置參数变量的作用主要用于脚本的传参. 位置參数变量的名称和作用都是确定不能改变的,但是该变量的内容是可以更改的,也就是变 ...

  6. 腾讯产品快速尝鲜,蓝鲸智云社区版V6.1灰度测试开启

    这周小鲸悄悄推送了社区版V6.1(二进制部署版本,包含基础套餐.监控日志套餐),没过一天就有用户来问6.1的使用问题了.小鲸大吃一鲸,原来你还是爱我的. ![请添加图片描述](https://img- ...

  7. 手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(一) - 介绍

    项目简介 novel 是一套基于时下最新 Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的学习型小说项目,配备详细的项目教程手把手教你从零开始开发上线一个生产级别的 J ...

  8. 《Effective C++》阅读总结(二):类的构造、析构和赋值

    今天是周六早上,但很不幸待会儿还是要去公司,本月kpi还剩一些工作要做,这个月计划的Effective C++学习,也基本完成了,最后一章节模板相关那部分还看不太懂,就大概过了一遍.现在是收尾总结阶段 ...

  9. 使用JavaCV实现读取视频信息及自动截取封面图

    概述 最近在对之前写的一个 Spring Boot 的视频网站项目做功能完善,需要利用 FFmpeg 实现读取视频信息和自动截图的功能,查阅资料后发现网上这部分的内容非常少,于是就有了这篇文章. 视频 ...

  10. 【Redis】简单动态字符串SDS

    C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"; // 也可以添加 ...