简要题意

你需要维护一个并查集,支持版本回退,查连通性,合并两个点。

特别的,没进行一次操作都要新建一个版本。

前置知识

找根(find)

现在我们来考虑如何find。

首先我们来研究以下正常的并查集是怎么写的(不加任何优化):

int find(int x){
if(fa[x]==x){
return x;
}
else{
return find(fa[x]);
}
}

事实上,我们只需要将 fa 替换成用可持久化数组维护,那么就可以实现可持久化了。(显然)

合并(merge)

同上,我们研究一下正常的并查集是怎么写的(同样,不加任何优化):

void merge(int x,int y){
fa[find(x)]=find(y); // 将 x 合并到 y 上。
}

大家可能回想,那我也吧 fa 改成可持久化的不就行了吗?

如果这样子……

合并的优化

如果直接暴力合并,那么会TLE。因为链就可以将你的 find 轻松卡到 \(O(n\log n)\)。

回忆一下并查集的优化,有下面这几种方式:

  • 路径压缩
  • 按秩合并

首先,不能路径压缩,因为路径压缩时间复杂度是均摊的,可以被人卡到 \(O(n\log n)\)。(同样,基于均摊复杂度的珂朵莉树、Splay 都不能简单的可持久化)

其次我们考虑按秩合并,其中的“秩”有下面几种:

  • 随机,等到合并时修改,小的合并到大的。
  • 按子树大小,小的合并到大的。
  • 按子树最大深度(就是子树树高),小的合并到大的。

随机方案貌似被人Hack了。我们就用第二个吧(因为第三个不会写)。

那么我们又要开一个可持久化数组(建议用结构体封装),维护子树大小,记得合并后我们更新一下(就是将新的根的子树大小加上旧的根的子树大小)。

对于证明过程,@chenxinyang2006 的题解已经写的很清楚了,我就不赘述了。

实现操作与时间复杂度分析

  • 合并就用上面介绍的。
  • 查连通性我们就找两个节点的根,看它们是不是一样的。
  • 回退版本我们就复制一个到当前版本即可。

时间复杂度,可持久化数组读写复杂度都是 \(O(\log n)\),那么:

  • 回退版本时间复杂度是 \(O(1)\) 的(因为只要普通数组赋值)。
  • 找根的时间复杂度本身是 \(O(\log n)\) (优化了),乘上可持久化的时间复杂度就是 \(O(\log^{2}n)\)。
  • 所以,查连通性时间复杂度是 \(O(\log^{2} n)\)。
  • 合并的时间复杂度也是 \(O(\log^{2}n)\)(瓶颈在找根)。

整体时间复杂度为 \(O(n+m\log^{2}n)\)。可以通过本题。

代码

本代码封装了可持久化数组和并查集,供大家参考。

#include<bits/stdc++.h>
using namespace std; int n,m; namespace PersistentUnionFind{
struct PersistentArray {
#define mid ((l+r)>>1)
const static int SIZE = 1e5 + 5;
struct {
int l, r, v;
} t[SIZE * 25];
int top;
int root[SIZE * 25];
int a[SIZE];
int newnode(int i) {
t[++top] = t[i];
return top;
}
int build(int i, int l, int r) {
i = (++top);
if (l == r) {
t[i].v = a[l];
return i;
}
t[i].l = build(t[i].l, l, mid);
t[i].r = build(t[i].r, mid + 1, r);
return i;
}
int update(int i, int l, int r, int p, int val) {
i = newnode(i);
if (l == r) {
t[i].v = val;
return i;
}
if (p <= mid) {
t[i].l = update(t[i].l, l, mid, p, val);
} else {
t[i].r = update(t[i].r, mid + 1, r, p, val);
}
return i;
}
int query(int i, int l, int r, int p) {
if (l == r) {
return t[i].v;
}
if (p <= mid) {
return query(t[i].l, l, mid, p);
} else {
return query(t[i].r, mid + 1, r, p);
}
}
inline int Assign(int i, int version, int p, int val) {
return update(root[version], 1, n, p, val);
}
inline int Get(int i, int version, int p) {
return query(root[version], 1, n, p);
}
void copyVersion(int new_,const int dst){
root[new_]=root[dst];
}
void newVersionFromPoint(int pos,int val){
root[pos]=val;
}
} fa,siz;
int find(int x,int version){
if(fa.Get(114514,version,x)==x){
return x;
}
else{
return find(fa.Get(1919810,version,x),version);
}
}
void merge(int x,int y,int version){
int fx=find(x,version),fy=find(y,version);
if(fx==fy)return;
int xsiz=siz.Get(114514,version,fx),ysiz=siz.Get(1919810,version,fy);
if(xsiz<=ysiz){
fa.newVersionFromPoint(version,fa.Assign(114514,version,fx,fy));
siz.newVersionFromPoint(version,siz.Assign(114514,version,fy,xsiz+ysiz));
}
else{
fa.newVersionFromPoint(version,fa.Assign(114514,version,fy,fx));
siz.newVersionFromPoint(version,siz.Assign(114514,version,fx,xsiz+ysiz));
}
}
bool same(int x,int y,int version){
return find(x,version)==find(y,version);
}
} signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
PersistentUnionFind::fa.a[i]=i;
PersistentUnionFind::siz.a[i]=1;
}
PersistentUnionFind::fa.newVersionFromPoint(0,PersistentUnionFind::fa.build(1,1,n));
PersistentUnionFind::siz.newVersionFromPoint(0,PersistentUnionFind::siz.build(1,1,n));
for(int i=1;i<=m;i++){
int op,x,y;
cin>>op>>x;
PersistentUnionFind::fa.copyVersion(i,i-1);
PersistentUnionFind::siz.copyVersion(i,i-1);
if(op==1){
cin>>y;
PersistentUnionFind::merge(x,y,i);
}
if(op==2){
PersistentUnionFind::fa.copyVersion(i,x);
PersistentUnionFind::siz.copyVersion(i,x);
}
if(op==3){
cin>>y;
cout<<PersistentUnionFind::same(x,y,i)<<'\n';
}
}
return 0;
}

AC Record

可持久化并查集学习笔记 | 题解P3402 可持久化并查集的更多相关文章

  1. Mysql学习笔记(六)增删改查

    PS:数据库最基本的操作就是增删改查了... 学习内容: 数据库的增删改查 1.增...其实就是向数据库中插入数据.. 插入语句 insert into table_name values(" ...

  2. Redis 学习笔记4: Redis 3.2.1 集群搭建

    在CenOS 6.7 linux环境下搭建Redis 集群环境 1.下载最新的Redis版本 本人下载的Redis版本是3.2.1版本,下载之后,解压,编译(make): 具体操作可以参考我的博文:R ...

  3. 一步一步学习Unity3d学习笔记系1.3 英雄联盟服务器集群架构猜想

    说到了网游那就涉及到服务器了,时下最火的属英雄联盟了,我也是它的粉丝,每周必撸一把,都说小撸怡情,大撸伤身,强撸灰飞烟灭,也告诫一下同仁们,注意身体,那么他的服务器架构是什么呢,给大家分享一下, 具体 ...

  4. Dubbo入门到精通学习笔记(十五):Redis集群的安装(Redis3+CentOS)、Redis集群的高可用测试(含Jedis客户端的使用)、Redis集群的扩展测试

    文章目录 Redis集群的安装(Redis3+CentOS) 参考文档 Redis 集群介绍.特性.规范等(可看提供的参考文档+视频解说) Redis 集群的安装(Redis3.0.3 + CentO ...

  5. 论文学习笔记 - 高光谱 和 LiDAR 融合分类合集

    A³CLNN: Spatial, Spectral and Multiscale Attention ConvLSTM Neural Network for Multisource Remote Se ...

  6. hibernate学习笔记(2)持久化类测试

    持久化类的创建: 创建一个共有的不带参数的构造方法: public void Students(){ } 创建一个带参数的构造方法: (快捷键创建) 生成get,set方法: *可以不用此方法创建持久 ...

  7. Dubbo入门到精通学习笔记(十四):ActiveMQ集群的安装、配置、高可用测试,ActiveMQ高可用+负载均衡集群的安装、配置、高可用测试

    文章目录 ActiveMQ 高可用集群安装.配置.高可用测试( ZooKeeper + LevelDB) ActiveMQ高可用+负载均衡集群的安装.配置.高可用测试 准备 正式开始 ActiveMQ ...

  8. MongoDB 学习笔记(三) MongoDB (replica set) 集群配置

    MongoDB Replica Sets的结构类似于以集群,完全可以把他当成一个集群,因为他确实与集群实现的作用是一样的:如果其中一个节点出现故障,其他的节点会马上将业务接管过来.而无需停机操作 Mo ...

  9. kettle学习笔记(九)——子转换、集群与变量

    一.概述 kettle中3个重要的步骤: 子转换/映射 在转换里调用一个子转换,便于封装和重用. 集群 集群模式 变量和参数 变量和参数的用法 二.子转换 1.定义子转换 主要由映射输入与映射输出定义 ...

  10. 续并查集学习笔记——Closing the farm题解

    在很多时候,并查集并不是一个完整的解题方法,而是一种思路. 通过以下题目来体会并查集逆向运用的思想. Description Farmer John and his cows are planning ...

随机推荐

  1. JS前端防止F12扒取源码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. fun无处不在 搜索结果调用其他字段的办法

    一直有人要搜索结果调用其他字段的信息 比如图片等等 一直忙没顾上 官方其实已经内置了一个内容读取器. 我们打开搜索的结果模板template\index_style\你的模板目录\search\ind ...

  3. CSS clear both清除浮动

    .clear{clear:both;} <div class="clear"></div>

  4. k8s运维之pod排错

    k8s运维之pod排错 K8S是一个开源的,用于管理云平台中多个主机上的容器化应用,Kubernetes的目标是让部署容器化变得简单并且高效 K8S的核心优势: 1,基于yaml文件实现容器的自动创建 ...

  5. 20_Vue如何监测数组类型数据发生改变的?

    通过上一节,我们知道了vue检测对象数据发生改变的原理 但是还有个api我们没有讲解,Vue.set(): 这个API比较适合在理解了对象检测的原理后进行讲解 案例准备 html <!-- 创建 ...

  6. 网络协议之:redis protocol 详解

    目录 简介 redis的高级用法 Redis中的pipline Redis中的Pub/Sub RESP protocol Simple Strings Bulk Strings RESP Intege ...

  7. 记录redis集群连接超时问题及解决方案

    下午同事反馈,某业务场景性能测试过程中,出现异常,提供日志报: Redis command timed out 1. 先看下日志 org.springframework.dao.QueryTimeou ...

  8. 网页嵌入zabbix页面(不同域名)

    先来结论: 方案一:绕过身份验证:https://www.cnblogs.com/JaSonS-toy/p/4939805.html(我不是这样实现,可以自行尝试) 方案二: 1.保证请求的ip与请求 ...

  9. Blazor和Vue对比学习(进阶.路由导航一):基本使用

    Blazor和Vue都是单文件组件SPA,路由的实现逻辑非常相似,页面路径的改变都是组件的切换,但因为各自语言的特性,在实现方式上有较大差异. 一.安装 1.Vue:Router是Vue的一个插件.如 ...

  10. Kafka Connect学习

    一.基础介绍 1.概念 2.Debezium 为捕获数据更改(change data capture,CDC)提供了一个低延迟的流式处理平台.可以消费数据库每一个行级别(row-level)的更改. ...