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

用一个映射来表示元素和并查集中序号的关系,代码中用\(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

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 4000010;
  4. int fa[N],to[N],sz[N],cnt,a[N],b[N],tot;
  5. int n,m,k,x,y;
  6. int ok[N];
  7. int find(int x){
  8. return x == fa[x]?x:fa[x] = find(fa[x]);
  9. }
  10. void merge(int x,int y){
  11. int a = find(to[x]);
  12. int b = find(to[y]);
  13. if(a==b) return;
  14. fa[b] = a;
  15. sz[a] += sz[b]; sz[b] = 0;
  16. }
  17. // 将x从原集合删除,加入到 y 所属的集合
  18. void update(int x,int y){
  19. sz[find(to[x])]--;
  20. to[x] = ++cnt;//给x分配新的 id
  21. sz[cnt] = 1;
  22. fa[cnt] = cnt;
  23. merge(x,y);
  24. }
  25. int main(){
  26. scanf("%d%d",&n,&m);cnt = n;
  27. for(int i=1;i<=n;i++)fa[i] = i,to[i] = i,sz[i] = 1;
  28. for(int i=1;i<=m;i++){
  29. scanf("%d%d",&k,&x);if(k!=3)scanf("%d",&y);
  30. if(k == 5){
  31. a[++tot] = x;
  32. b[tot] = y;continue;
  33. }
  34. if(k == 1)merge(x,y);
  35. if(k == 2)update(x,y);
  36. if(k == 4){
  37. if(find(to[x]) == find(to[y]))printf("Yes\n");
  38. else printf("No\n");
  39. }
  40. if(k == 3)printf("%d\n",sz[find(to[x])]-1);
  41. }
  42. for(int i=1;i<=tot;i++){
  43. if(find(to[a[i]]) == find(to[b[i]])) ok[find(to[a[i]])] = 1;
  44. }
  45. int res = -1;
  46. for(int i=1;i<=n;i++)
  47. if(!ok[find(to[i])])res = max(res,sz[find(to[i])]);
  48. cout<<res<<endl;
  49. return 0;
  50. }

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

[2019南昌区域赛A]

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

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

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int inf = 0x3f3f3f3f;
  5. #define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
  6. void err() { cout << "\033[39;0m" << endl; }
  7. template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
  8. const int N = 1000000 + 50;
  9. struct Node{
  10. int op, x, y;
  11. }o[N];
  12. vector<int> g[N];
  13. int n, m;
  14. int fa[N*2], dep[N*2], sz[N*2], to[N], tot;
  15. int res[N];
  16. int find(int x){
  17. if(x == fa[x]) return x;
  18. return find(fa[x]);
  19. }
  20. /*
  21. 1 k a b merge(a, b)
  22. 2 k a del(a)
  23. 3 k a b merge(a,b)
  24. 4 k a b find(a) == find(b)
  25. 5 k a sz[a]
  26. */
  27. void dfs(int u){
  28. for(auto id : g[u]){
  29. int op = o[id].op, x = o[id].x, y = o[id].y;
  30. if(op == 1){
  31. if(to[x] == -1 || to[y] == -1){
  32. dfs(id);
  33. continue;
  34. }
  35. int rx = find(to[x]), ry = find(to[y]);
  36. if(rx == ry){ dfs(id); continue;}
  37. else if(dep[rx] == dep[ry]){
  38. fa[ry] = rx; sz[rx] += sz[ry]; dep[rx] ++;
  39. dfs(id);
  40. fa[ry] = ry; sz[rx] -= sz[ry]; dep[rx] --;
  41. } else {
  42. if(dep[rx] < dep[ry]) swap(rx, ry);
  43. fa[ry] = rx; sz[rx] += sz[ry];
  44. dfs(id);
  45. fa[ry] = ry; sz[rx] -= sz[ry];
  46. }
  47. } else if(op == 2){
  48. if(to[x] == -1) { dfs(id); continue; }
  49. int rx = find(to[x]);
  50. int t = to[x];
  51. to[x] = -1;
  52. sz[rx] --;
  53. dfs(id);
  54. to[x] = t;
  55. sz[rx] ++;
  56. } else if(op == 3){
  57. if(to[x] == - 1 || to[y] == -1) {dfs(id); continue; }
  58. int t = to[x];
  59. int rx = find(to[x]), ry = find(to[y]);
  60. sz[rx]--;
  61. to[x] = ++tot;
  62. sz[to[x]] = 1;
  63. fa[to[x]] = ry;
  64. sz[ry] ++;
  65. dfs(id);
  66. sz[ry] --;
  67. sz[rx] ++;
  68. to[x] = t;
  69. } else {
  70. if(op == 4){
  71. if(to[x] == -1 || to[y] == -1){
  72. res[id] = 0;
  73. } else {
  74. res[id] = find(to[x]) == find(to[y]);
  75. }
  76. } else if(op == 5){
  77. if(to[x] == -1) res[id] = 0;
  78. else {
  79. res[id] = sz[find(to[x])];
  80. }
  81. }
  82. dfs(id);
  83. }
  84. }
  85. }
  86. int main(){
  87. scanf("%d%d", &n, &m);
  88. for(int i=1;i<=m;i++){
  89. int op, k, x, y = 0;scanf("%d%d%d", &op, &k, &x);
  90. g[k].push_back(i);
  91. if(op != 2 && op != 5) scanf("%d", &y);
  92. o[i] = {op, x, y};
  93. }
  94. for(int i=1;i<=n;i++) to[i] = i, fa[i] = i, sz[i] = 1;
  95. tot = n;
  96. dfs(0);
  97. for(int i=1;i<=m;i++){
  98. if(o[i].op == 4) puts(res[i] ? "Yes" : "No");
  99. else if(o[i].op == 5){
  100. printf("%d\n", res[i]);
  101. }
  102. }
  103. return 0;
  104. }

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

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

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

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

  1. const int N = 200000 + 5;
  2. int n, m;
  3. struct TreeNode{
  4. int l, r;
  5. }t[N*30];
  6. int root[N], tot;
  7. int fa[N*30], dep[N*30];
  8. void build(int &p, int l, int r){
  9. p = ++tot;
  10. if(l == r){fa[p] = l;return;}
  11. int mid = l + r >> 1;
  12. build(t[p].l, l, mid);
  13. build(t[p].r, mid+1, r);
  14. }
  15. // 修改p版本的pos位置的fa值
  16. void change(int last, int &p, int l, int r, int pos, int val){
  17. p = ++tot;
  18. t[p] = t[last];
  19. if(l == r){
  20. fa[p] = val;
  21. dep[p] = dep[last];
  22. return;
  23. }
  24. int mid = l + r >> 1;
  25. if(pos <= mid)
  26. change(t[last].l, t[p].l, l, mid, pos, val);
  27. else
  28. change(t[last].r, t[p].r, mid+1, r, pos, val);
  29. }
  30. int getIndex(int p, int l, int r,int pos){
  31. if(l == r) return p;
  32. int mid = l + r >> 1;
  33. if(pos <= mid) return getIndex(t[p].l, l, mid, pos);
  34. else return getIndex(t[p].r, mid+1, r, pos);
  35. }
  36. int find(int p, int x){
  37. int index = getIndex(p, 1, n, x);
  38. if(fa[index] == x) return index; // 返回祖先节点下标
  39. return find(p, fa[index]);
  40. }
  41. void merge(int last, int &p, int x, int y){
  42. int fx = find(p, x), fy = find(p, y);
  43. if(fa[fx] == fa[fy]) return;
  44. // x-> y合并,需要满足 dep[x]<dep[y]
  45. if(dep[fx] > dep[fy]) swap(fx, fy);
  46. change(last, p, 1, n, fa[fx], fa[fy]);
  47. if(dep[fx] == dep[fy]){
  48. dep[getIndex(p, 1, n, fa[fy])]++;
  49. }
  50. }
  51. int main(){
  52. scanf("%d%d", &n, &m);
  53. build(root[0], 1, n);
  54. for(int i=1;i<=m;i++){
  55. root[i] = root[i-1];
  56. int op, x, y;scanf("%d%d", &op, &x);
  57. if(op == 1){
  58. scanf("%d", &y);
  59. merge(root[i], root[i], x, y);
  60. }
  61. else if(op == 2) root[i] = root[x];
  62. else {
  63. scanf("%d", &y);
  64. int fx = find(root[i], x), fy = find(root[i], y);
  65. if(fa[fx] == fa[fy]) puts("1");
  66. else puts("0");
  67. }
  68. }
  69. return 0;
  70. }

例题: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. 使用javafx开发一款桌面个性化软件

    本来笔者只是打算开发一个显示在桌面的cpu和内存监控工具,没想到迭代了几次版本之后变成了桌面个性化工具了. 目前实现功能: cpu和内存的实时监控 开机自动启动 自定义logo 自定义主题颜色 鼠标拖 ...

  2. ORA-28001: the password has expired解决方法

    Oracle提示错误消息ORA-28001: the password has expired,是由于Oracle11G的新特性所致, Oracle11G创建用户时缺省密码过期限制是180天(即6个月 ...

  3. App控件定位

    本文将分享Android相关基础知识和Android APP控件定位工具的使用方法. Android基础知识 Android布局 Android是通过容器的布局属性来管理子控件的位置关系(iOS去掉了 ...

  4. python模块详解 | pyquery

    简介 pyquery是一个强大的 HTML 解析库,利用它,我们可以直接解析 DOM 节点的结构,并通过 DOM 节点的一些属性快速进行内容提取. 官方文档:http://pyquery.readth ...

  5. python学习笔记 | selenium各浏览器驱动下载地址

    Chrome http://chromedriver.storage.googleapis.com/index.html 不同的Chrome的版本对应的chromedriver.exe 版本也不一样, ...

  6. 【Linux】linux中用vim来比较文件内容不同

    1. 使用vim的比较模式打开两个文件: vim -d file1 file2 或 vimdiff file1 file2 2. 如果已经打开了文件file1,再打开另一个文件file2进行比较: : ...

  7. Netty学习:ChannelHandler执行顺序详解,附源码分析

    近日学习Netty,在看书和实践的时候对于书上只言片语的那些话不是十分懂,导致尝试写例子的时候遭遇各种不顺,比如decoder和encoder还有HttpObjectAggregator的添加顺序,研 ...

  8. web dynpro配置注意事项

    如果你想使用web dynpro 开发的应用,但是发现浏览器报错,那么你按照下面的步骤逐一进行检查吧.特别是返回的500错误,或者是你发现浏览器的地址栏中以http://<hostname> ...

  9. luoguP2016 战略游戏

    题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题.他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了 ...

  10. 卷积神经网络学习笔记——SENet

    完整代码及其数据,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/DeepLearningNote 这里结合网络的资料和SE ...