知识点
数据结构之并查集-CSDN博客https://blog.csdn.net/gege_0606/article/details/139069814?spm=1001.2014.3001.5501
题型一
题目链接:食物链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
分析
题目中给出了三类动物A,B,C,关系如下图所示
对于传统的并查集,其只能表示单一的身份或者集合,但在本题当中,显然,我们需要表示捕食者,被捕食者和中立者,那么我们应该怎么办呢?
这个时候我们就可以用到种类并查集了,我们把并查集分成三个部分,1~n表示一种类群,n~2n表示一种类群,2n~3n表示一种类群。
- x:代表动物本身作为某一类型(比如类型B)。
- x + n:代表作为另一类型(比如类型A),这个类型是直接吃x表示的类型B。
- x + 2 * n:代表作为第三种类型(比如类型C),这个类型是被类型B吃的,或者是吃类型A的类型。
题目的要求输出假话的个数,假话分以下几种类别
接下来就是根据输入的type来决定x和y之间的关系。
type==1如果x与y是同类关系。
什么时候会跟前面的真话冲突呢?
如果吃x(类型B)的类型A和y属于同一类,冲突
如果类型C(被x吃的类型)和y属于同一类别,冲突
不冲突
合并的时候要把所有的映射关系都合并,合并x,y;合并x+n,y+n;合并x+2*n,y+2*n;
type==2如果x吃y。
那么什么时候会跟前面的真话冲突呢?
如果x和y属于同一个类群,冲突
如果吃x的类型A和y属于同一个类群,冲突
如果x和y相等,自己不可能吃自己,冲突
不冲突
合并所有的映射关系,x和y+n,x+n和y+2*n,x+2*n和y;
代码
#include <bits/stdc++.h>
using namespace std;
/*int read()
{
int f=1,w=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while('0'<=ch&&ch<='9'){w=w*10+ch-48;ch=getchar();}
// cout<<f*w<<endl;
return f*w;
}*/
const int N = 100005; // 最大可能的动物数量
int pre[3 * N]; // 并查集数组,每个动物有三种可能的状态
// 查找根节点,并实施路径压缩
int root(int x) {
if (pre[x] != x) pre[x] = root(pre[x]);
return pre[x];
}
// 合并两个集合
void Union(int x, int y) {
int rootX = root(x);
int rootY = root(y);
if (rootX != rootY) pre[rootY] = rootX;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, k;
cin>>n>>k;
int ans = 0;
// 初始化并查集
for (int i = 0; i < 3 * n; i++) {
pre[i] = i;
}
while (k--) {
int type, x, y;
if (x > n || y > n || x < 1 || y < 1) { // 检查 x 和 y 是否在有效范围内
ans++;
continue;
}
if (type == 1) { // X 和 Y 是同类
if (root(x+n) == root(y) || root(x +2* n) == root(y)) {
ans++;
} else {
Union(x, y);
Union(x + n, y + n);
Union(x + 2 * n, y + 2 * n);
}
} else if (type == 2) { // X 吃 Y
if (x == y || root(x) == root(y) || root(y) == root(x + n)) {
ans++;
} else {
Union(x+2*n, y );
Union(x + n, y+2*n);
Union(x , y + n);
}
}
}
cout << ans << '\n';
return 0;
}
题目二
P1196 [NOI2002] 银河英雄传说 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
分析
以下两张图片是题目的要求
对于命令M,我们只需要使用并查集的基本操作,合并两个根节点即可 。
对于命令C,我们不仅要判断i和j战舰是否在同一列中,还要输出它们之间布置多少的战舰。对于判断是否是在同一列中,也只需要运用到并查集的基本操作,找根(查找这一操作)。
那么我们如何计算i和j战舰之间有多少的战舰呢?
可以想到需要维护一个数组,记录每个叶子结点(战舰)相对于根的距离。以及一个size数组来维护每一列的元素的个数。
接下来就是find函数和Union(合并)函数了;
对于find函数
我们不仅每一次都需要递归找到它的根节点,还需要每次更新每个战舰距离根节点的距离。
int find(int x) {
if (parent[x] == x) {
return x;
}
int original_parent = parent[x];
parent[x] = find(parent[x]);
offset[x] += offset[original_parent];
return parent[x];
}
代码解释:
变量的含义:
original_parent表示的是x的直接父亲节点。
offset数组通过累加记录了每一个元素与根节点的距离。
offset[x] += offset[original_parent];
假设有节点链x -> a -> b -> root
,其中->
表示指向父节点,初始时每个节点到其父节点的offset
为0(或者在之前的操作中设置了某些值)。在调用find(x)
时,会发生以下步骤:
- 递归调用
find(a)
,接着是find(b)
,最终到find(root)
。 - 在递归回溯过程中:
- 首先,
b
是root
的直接子节点,offset[b]
更新为b
到root
的距离。 - 然后,
a
的offset
需要更新为a
到b
的距离加上b
到root
的距离(offset[b]
),最终是a到根的距离。 - 最后,
x
的offset
更新为x
到a
的距离加上更新后的offset[a],最终是x到根的距离
。
- 首先,
这个过程确保,每个节点的offset
最终反映的是从该节点到根节点的总距离,而不仅仅是到直接父节点的距离。
合并函数:
void Union(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
offset[rootX] = size[rootY];
size[rootY] += size[rootX];
}
}
代码解释:
parent[rootX] = rootY;
:这行代码的作用是将rootX
的父节点指向rootY
,即将rootX
集合合并到rootY
集合下,此时rootY
成为了合并后的集合的新根节点。offset[rootX] = size[rootY];
:当将rootX
接到rootY
后面时,我们需要为rootX
设置一个偏移量,这个偏移量是rootY
集合当前的大小。这样做的目的是确保rootX
及其所有子节点的位置能正确反映它们相对于新根节点rootY
的位置。
为什么用size[rootY]
作为偏移量?
因为size[rootY]
表示rootY
集合中元素的数量,如果我们把rootX
放在rootY
的所有元素后面,那么rootX
到rootY
的距离就应该是rootY
集合的大小。
size[rootY] += size[rootX];
:在合并两个集合后,新的根节点rootY
所在的集合的大小应该是原来两个集合大小的总和。这行代码更新了rootY
集合的大小,将rootX
集合的大小加到了rootY
上。
代码
#include <iostream>
#include <cstdlib>
#include <cmath>
const int MAXN = 30001;
int parent[MAXN], offset[MAXN], size[MAXN];
int find(int x) {
if (parent[x] == x) {
return x;
}
int original_parent = parent[x];
parent[x] = find(parent[x]);
offset[x] += offset[original_parent];//offset表示x距离根节点的距离
return parent[x];
}
void Union(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
offset[rootX] = size[rootY];
size[rootY] += size[rootX];
}
}
int main() {
std::ios::sync_with_stdio(false);
int n;
std::cin >> n;
//初始化
for (int i = 1; i < MAXN; i++) {
parent[i] = i;
offset[i] = 0;
size[i] = 1;
}
char op;
int x, y;
for (int i = 0; i < n; i++) {
std::cin >> op >> x >> y;
if (op == 'M') {
Union(x, y);
} else if (op == 'C') {
if (find(x) != find(y)) {
std::cout << "-1\n";
} else {
std::cout << abs(offset[x] - offset[y]) - 1 << "\n";
}
}
}
return 0;
}