并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
并查集支持两种操作:
合并(Union):合并两个元素所属集合(合并对应的树)
查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合
并查集在经过修改后可以支持单个元素的删除、移动;使用动态开点线段树还可以实现可持久化并查集。
// https://www.luogu.com.cn/problem/UVA11987
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
struct Dsu
{
// pa[x]: x的父节点
// size[x]: 以x为根的子树的节点个数
// sum[x]: 以x为根的子树的节点值之和
vector<size_t> pa, size, sum;
/* 初始时,每个元素都位于一个单独的集合,表示为一棵只有根节点的树,本题包含并查集移动操作,预先为每个节点制作副本,并将其副本作为父亲 */
explicit Dsu(size_t sz) : pa(sz<<1), size(sz<<1, 1), sum(sz<<1)
{
iota(pa.begin(), pa.begin() + sz, sz);
iota(pa.begin() + sz, pa.end(), sz);
iota(sum.begin() + sz, sum.end(), 0);
}
/*如果两个元素的集合号不同,将两个元素合并为一个集合,
合并时只需要把一个元素的根结点集合号,改为另一个元素的根结点的集合号,只改根结点的集合号。 将节点较少或深度较小的树连到另一棵,以免发生退化 */
void unite(size_t x, size_t y)
{
// 查找当前元素的根结点
x = find(x), y = find(y);
if (x == y)
return;
if (size[x] < size[y])
swap(x, y);
pa[y] = x;
size[x] += size[y];
sum[x] += sum[y];
}
/*查找时,采用递归的方法找其根结点,根结点的集合号等于自己时停止。
在回归时,把当前结点到根结点路径上的所有结点的集合号统一为根结点的集合号。
路径压缩: 查询过程中经过的每个元素都属于该集合,我们可以将其直接连到根节点以加快后续查询 */
size_t find(size_t x)
{
return pa[x] == x ? x : pa[x] = find(pa[x]);
}
/* 要删除一个叶子节点,我们可以将其父亲设为自己。为了保证要删除的元素都是叶子,我们可以预先为每个节点制作副本,并将其副本作为父亲 */
void erase(size_t x)
{
--size[find(x)];
pa[x] = x;
}
/* 移动: 通过以副本作为父亲,保证要移动的元素都是叶子 */
void move(size_t x, size_t y)
{
auto fx = find(x), fy = find(y);
if (fx == fy)
return;
pa[x] = fy;
--size[fx], ++size[fy];
sum[fx] -= x;
sum[fy] += x;
}
};
int main()
{
int n, m, k, i, p, q;
while (cin >> n >> m)
{
Dsu dsu(n+1); // 元素范围是 1..n, [1]'s backup is [n+2]
for (i = 0; i < m; ++i)
{
cin >> k;
switch (k)
{
case 1:
cin >> p >> q;
dsu.unite(p, q);
break;
case 2:
cin >> p >> q;
dsu.move(p, q);
break;
case 3:
cin >> p;
p = dsu.find(p);
cout << dsu.size[p] << " " << dsu.sum[p] << endl;
break;
}
}
}
return 0;
}