带撤销并查集支持从某个元素从原来的集合中撤出来,然后加入到一个另外一个集合中,或者删除该元素

用一个映射来表示元素和并查集中序号的关系,代码中用\(to[x]\) 表示x号元素在并查集中的 id

删除 x 号元素时,需要将 \(to[x]\) 的集合大小减去1,然后令 \(to[x]=-1\) 标记 x 删除即可

如果要重新加入一个元素,那么给x分配一个新的 id,\(to[x] = newid\)

例题1:https://www.cometoj.com/contest/33/problem/E?problem_id=1459

#include <bits/stdc++.h>
using namespace std;
const int N = 4000010;
int fa[N],to[N],sz[N],cnt,a[N],b[N],tot;
int n,m,k,x,y;
int ok[N];
int find(int x){
return x == fa[x]?x:fa[x] = find(fa[x]);
}
void merge(int x,int y){
int a = find(to[x]);
int b = find(to[y]);
if(a==b) return;
fa[b] = a;
sz[a] += sz[b]; sz[b] = 0;
}
// 将x从原集合删除,加入到 y 所属的集合
void update(int x,int y){
sz[find(to[x])]--;
to[x] = ++cnt;//给x分配新的 id
sz[cnt] = 1;
fa[cnt] = cnt;
merge(x,y);
}
int main(){
scanf("%d%d",&n,&m);cnt = n;
for(int i=1;i<=n;i++)fa[i] = i,to[i] = i,sz[i] = 1;
for(int i=1;i<=m;i++){
scanf("%d%d",&k,&x);if(k!=3)scanf("%d",&y);
if(k == 5){
a[++tot] = x;
b[tot] = y;continue;
}
if(k == 1)merge(x,y);
if(k == 2)update(x,y);
if(k == 4){
if(find(to[x]) == find(to[y]))printf("Yes\n");
else printf("No\n");
} if(k == 3)printf("%d\n",sz[find(to[x])]-1);
} for(int i=1;i<=tot;i++){
if(find(to[a[i]]) == find(to[b[i]])) ok[find(to[a[i]])] = 1;
}
int res = -1;
for(int i=1;i<=n;i++)
if(!ok[find(to[i])])res = max(res,sz[find(to[i])]);
cout<<res<<endl;
return 0;
}

例题2:https://nanti.jisuanke.com/t/42576

[2019南昌区域赛A]

不要求在线的可持久化操作,可以离线处理询问,按照正常的时间顺序维护并查集,由于需要回溯,所以不能使用路径压缩

复杂度 \(O(m\log n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1000000 + 50;
struct Node{
int op, x, y;
}o[N];
vector<int> g[N];
int n, m;
int fa[N*2], dep[N*2], sz[N*2], to[N], tot;
int res[N];
int find(int x){
if(x == fa[x]) return x;
return find(fa[x]);
}
/*
1 k a b merge(a, b)
2 k a del(a)
3 k a b merge(a,b)
4 k a b find(a) == find(b)
5 k a sz[a]
*/
void dfs(int u){
for(auto id : g[u]){
int op = o[id].op, x = o[id].x, y = o[id].y;
if(op == 1){
if(to[x] == -1 || to[y] == -1){
dfs(id);
continue;
}
int rx = find(to[x]), ry = find(to[y]);
if(rx == ry){ dfs(id); continue;}
else if(dep[rx] == dep[ry]){
fa[ry] = rx; sz[rx] += sz[ry]; dep[rx] ++;
dfs(id);
fa[ry] = ry; sz[rx] -= sz[ry]; dep[rx] --;
} else {
if(dep[rx] < dep[ry]) swap(rx, ry);
fa[ry] = rx; sz[rx] += sz[ry];
dfs(id);
fa[ry] = ry; sz[rx] -= sz[ry];
}
} else if(op == 2){
if(to[x] == -1) { dfs(id); continue; }
int rx = find(to[x]);
int t = to[x];
to[x] = -1;
sz[rx] --;
dfs(id);
to[x] = t;
sz[rx] ++;
} else if(op == 3){
if(to[x] == - 1 || to[y] == -1) {dfs(id); continue; }
int t = to[x];
int rx = find(to[x]), ry = find(to[y]);
sz[rx]--;
to[x] = ++tot;
sz[to[x]] = 1;
fa[to[x]] = ry;
sz[ry] ++;
dfs(id);
sz[ry] --;
sz[rx] ++;
to[x] = t;
} else {
if(op == 4){
if(to[x] == -1 || to[y] == -1){
res[id] = 0;
} else {
res[id] = find(to[x]) == find(to[y]);
}
} else if(op == 5){
if(to[x] == -1) res[id] = 0;
else {
res[id] = sz[find(to[x])];
}
}
dfs(id);
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++){
int op, k, x, y = 0;scanf("%d%d%d", &op, &k, &x);
g[k].push_back(i);
if(op != 2 && op != 5) scanf("%d", &y);
o[i] = {op, x, y};
}
for(int i=1;i<=n;i++) to[i] = i, fa[i] = i, sz[i] = 1;
tot = n;
dfs(0);
for(int i=1;i<=m;i++){
if(o[i].op == 4) puts(res[i] ? "Yes" : "No");
else if(o[i].op == 5){
printf("%d\n", res[i]);
}
}
return 0;
}

可持久化并查集支持查询任一历史版本的信息。并查集信息用数组 \(fa\) 表示,合并集合时,基本操作有两种,一个是路径压缩(可能会修改很多个fa),一个是按秩合并(启发式合并思路,小的集合向大的合并,只会修改一次fa),由于要保存历史信息,按照可持久化的一贯思路,每一次操作都会新开 logn 的空间,所以这里要用按秩合并。

注意按秩合并的find函数写法也有所不同,不能修改fa

此时问题就是维护每个版本的fa数组

模版题:https://www.luogu.com.cn/problem/P3402

const int N = 200000 + 5;
int n, m;
struct TreeNode{
int l, r;
}t[N*30];
int root[N], tot;
int fa[N*30], dep[N*30];
void build(int &p, int l, int r){
p = ++tot;
if(l == r){fa[p] = l;return;}
int mid = l + r >> 1;
build(t[p].l, l, mid);
build(t[p].r, mid+1, r);
}
// 修改p版本的pos位置的fa值
void change(int last, int &p, int l, int r, int pos, int val){
p = ++tot;
t[p] = t[last];
if(l == r){
fa[p] = val;
dep[p] = dep[last];
return;
}
int mid = l + r >> 1;
if(pos <= mid)
change(t[last].l, t[p].l, l, mid, pos, val);
else
change(t[last].r, t[p].r, mid+1, r, pos, val);
}
int getIndex(int p, int l, int r,int pos){
if(l == r) return p;
int mid = l + r >> 1;
if(pos <= mid) return getIndex(t[p].l, l, mid, pos);
else return getIndex(t[p].r, mid+1, r, pos);
}
int find(int p, int x){
int index = getIndex(p, 1, n, x);
if(fa[index] == x) return index; // 返回祖先节点下标
return find(p, fa[index]);
}
void merge(int last, int &p, int x, int y){
int fx = find(p, x), fy = find(p, y);
if(fa[fx] == fa[fy]) return;
// x-> y合并,需要满足 dep[x]<dep[y]
if(dep[fx] > dep[fy]) swap(fx, fy);
change(last, p, 1, n, fa[fx], fa[fy]);
if(dep[fx] == dep[fy]){
dep[getIndex(p, 1, n, fa[fy])]++;
}
}
int main(){
scanf("%d%d", &n, &m);
build(root[0], 1, n);
for(int i=1;i<=m;i++){
root[i] = root[i-1];
int op, x, y;scanf("%d%d", &op, &x);
if(op == 1){
scanf("%d", &y);
merge(root[i], root[i], x, y);
}
else if(op == 2) root[i] = root[x];
else {
scanf("%d", &y);
int fx = find(root[i], x), fy = find(root[i], y);
if(fa[fx] == fa[fy]) puts("1");
else puts("0");
}
}
return 0;
}

例题:https://www.luogu.com.cn/problem/P4768

先跑一次最短路,然后按照边权从大到小用带权并查集维护集合到源点的最短距离。保留所有版本的信息,然后对于每次查询找到对应版本号回答问题即可

复杂度:\(O(n\log m + (m+q) \log^2n)\)

带撤销并查集 & 可持久化并查集的更多相关文章

  1. 算法笔记--可撤销并查集 && 可持久化并查集

    可撤销并查集模板: struct UFS { stack<pair<int*, int>> stk; int fa[N], rnk[N]; inline void init(i ...

  2. [bzoj3673][可持久化并查集 by zky] (rope(可持久化数组)+并查集=可持久化并查集)

    Description n个集合 m个操作 操作: 1 a b 合并a,b所在集合 2 k 回到第k次操作之后的状态(查询算作操作) 3 a b 询问a,b是否属于同一集合,是则输出1否则输出0 0& ...

  3. 【BZOJ3673/3674】可持久化并查集/可持久化并查集加强版 可持久化线段树

    [BZOJ3674]可持久化并查集加强版 Description Description:自从zkysb出了可持久化并查集后……hzwer:乱写能AC,暴力踩标程KuribohG:我不路径压缩就过了! ...

  4. BZOJ4358: permu(带撤销并查集 不删除莫队)

    题意 题目链接 Sol 感觉自己已经老的爬不动了.. 想了一会儿,大概用个不删除莫队+带撤销并查集就能搞了吧,\(n \sqrt{n} logn\)应该卡的过去 不过不删除莫队咋写来着?....跑去学 ...

  5. 【BZOJ】【3673】可持久化并查集 & 【3674】可持久化并查集加强版

    可持久化并查集 Orz hzwer & zyf 呃学习了一下可持久化并查集的姿势……其实并查集就是一个fa数组(可能还要带一个size或rank数组),那么我们对并查集可持久化其实就是实现一个 ...

  6. 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status ...

  7. bzoj3674 可持久化并查集

    我是萌萌的任意门 可持久化并查集的模板题-- 做法好像很多,可以标号法,可以森林法. 本来有O(mloglogn)的神算法(按秩合并+倍增),然而我这种鶸渣就只会写O(mlog2n)的民科算法--再加 ...

  8. 【BZOJ3673】&&【BZOJ3674】: 可持久化并查集 by zky 可持久化线段树

    没什么好说的. 可持久化线段树,叶子节点存放父亲信息,注意可以规定编号小的为父亲. Q:不是很清楚空间开多大,每次询问父亲操作后修改的节点个数是不确定的.. #include<bits/stdc ...

  9. 【BZOJ】3673: 可持久化并查集 by zky & 3674: 可持久化并查集加强版(可持久化线段树)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3674 http://www.lydsy.com/JudgeOnline/problem.php?id ...

随机推荐

  1. 【Java基础】基本语法-变量与运算符

    基本语法-变量与运算符 关键字和保留字 关键字定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词). 关键字特点:关键字中所有字母都为小写. 用于定义数据类型:class.interf ...

  2. LeetCode844 比较含退格的字符串

    题目描述: 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果. # 代表退格字符. 示例 1: 输入:S = "ab#c", T = ...

  3. 【C++】《C++ Primer 》第十二章

    第十二章 动态内存 目前为止,所使用的对象都有着严格定义的生存期. 全局对象在程序启动时分配,在程序结束时销毁.局部自动对象在进入其定义所在的程序块时被创建,在离开块时销毁.局部static对象在第一 ...

  4. LeetCode700 二叉搜索树中搜索

    给定二叉搜索树(BST)的根节点和一个值. 你需要在BST中找到节点值等于给定值的节点. 返回以该节点为根的子树. 如果节点不存在,则返回 NULL. 例如, 给定二叉搜索树: 4 / \ 2 7 / ...

  5. 【Problems】端口被占用 查看是被谁占用并关闭它

    文章目录 Windows Linux 经常在Windows.Linux环境下运行JavaWeb项目,Tomcat的端口被占用了. 端口被占用就查看是被谁占用关闭它就行. Windows 在Window ...

  6. SDUST数据结构 - 19级期末考试

    判断题: 选择题: 函数题: 6-1 统计二叉树叶子结点个数: 题目: 裁判测试程序样例: #include <stdio.h> #include <stdlib.h> typ ...

  7. SpringBoot WebSocket技术

    最近看了Spring in Action,了解了一下WebSocket和Stomp协议相关技术,并搭建了一个项目.网上的例子不完整或者描述不清,所以自己记录一下以作备忘. 一.配置 Spring Bo ...

  8. 人工智能"眼睛"——摄像头

    摄像头机器视觉人工智能的"眼睛",其重要性在嵌入式领域不言而喻.但是如何理解和使用摄像头却是一个非常棘手的问题.本文主要针对调试摄像头过程中遇到的问题,对摄像头的基本原理及概述进行 ...

  9. Python_ 1生成器(上)初识生成器

    引言:列表生成式 现在有个需求,给定列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],现在要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式 1 >>> a ...

  10. Any race is a bug. When there is a race, the compiler is free to do whatever it wants.

    https://mp.weixin.qq.com/s/pVJiFdDDKVx707eKL19bjA 谈谈 Golang 中的 Data Race 原创 ms2008 poslua 2019-05-13 ...