可持久化并查集学习笔记 | 题解P3402 可持久化并查集
简要题意
你需要维护一个并查集,支持版本回退,查连通性,合并两个点。
特别的,没进行一次操作都要新建一个版本。
前置知识
- 可持久化数组,如果您不会,出门左转 【模板】可持久化线段树 1(可持久化数组)。
- 并查集
找根(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;
}
可持久化并查集学习笔记 | 题解P3402 可持久化并查集的更多相关文章
- Mysql学习笔记(六)增删改查
PS:数据库最基本的操作就是增删改查了... 学习内容: 数据库的增删改查 1.增...其实就是向数据库中插入数据.. 插入语句 insert into table_name values(" ...
- Redis 学习笔记4: Redis 3.2.1 集群搭建
在CenOS 6.7 linux环境下搭建Redis 集群环境 1.下载最新的Redis版本 本人下载的Redis版本是3.2.1版本,下载之后,解压,编译(make): 具体操作可以参考我的博文:R ...
- 一步一步学习Unity3d学习笔记系1.3 英雄联盟服务器集群架构猜想
说到了网游那就涉及到服务器了,时下最火的属英雄联盟了,我也是它的粉丝,每周必撸一把,都说小撸怡情,大撸伤身,强撸灰飞烟灭,也告诫一下同仁们,注意身体,那么他的服务器架构是什么呢,给大家分享一下, 具体 ...
- Dubbo入门到精通学习笔记(十五):Redis集群的安装(Redis3+CentOS)、Redis集群的高可用测试(含Jedis客户端的使用)、Redis集群的扩展测试
文章目录 Redis集群的安装(Redis3+CentOS) 参考文档 Redis 集群介绍.特性.规范等(可看提供的参考文档+视频解说) Redis 集群的安装(Redis3.0.3 + CentO ...
- 论文学习笔记 - 高光谱 和 LiDAR 融合分类合集
A³CLNN: Spatial, Spectral and Multiscale Attention ConvLSTM Neural Network for Multisource Remote Se ...
- hibernate学习笔记(2)持久化类测试
持久化类的创建: 创建一个共有的不带参数的构造方法: public void Students(){ } 创建一个带参数的构造方法: (快捷键创建) 生成get,set方法: *可以不用此方法创建持久 ...
- Dubbo入门到精通学习笔记(十四):ActiveMQ集群的安装、配置、高可用测试,ActiveMQ高可用+负载均衡集群的安装、配置、高可用测试
文章目录 ActiveMQ 高可用集群安装.配置.高可用测试( ZooKeeper + LevelDB) ActiveMQ高可用+负载均衡集群的安装.配置.高可用测试 准备 正式开始 ActiveMQ ...
- MongoDB 学习笔记(三) MongoDB (replica set) 集群配置
MongoDB Replica Sets的结构类似于以集群,完全可以把他当成一个集群,因为他确实与集群实现的作用是一样的:如果其中一个节点出现故障,其他的节点会马上将业务接管过来.而无需停机操作 Mo ...
- kettle学习笔记(九)——子转换、集群与变量
一.概述 kettle中3个重要的步骤: 子转换/映射 在转换里调用一个子转换,便于封装和重用. 集群 集群模式 变量和参数 变量和参数的用法 二.子转换 1.定义子转换 主要由映射输入与映射输出定义 ...
- 续并查集学习笔记——Closing the farm题解
在很多时候,并查集并不是一个完整的解题方法,而是一种思路. 通过以下题目来体会并查集逆向运用的思想. Description Farmer John and his cows are planning ...
随机推荐
- JS前端防止F12扒取源码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- fun无处不在 搜索结果调用其他字段的办法
一直有人要搜索结果调用其他字段的信息 比如图片等等 一直忙没顾上 官方其实已经内置了一个内容读取器. 我们打开搜索的结果模板template\index_style\你的模板目录\search\ind ...
- CSS clear both清除浮动
.clear{clear:both;} <div class="clear"></div>
- k8s运维之pod排错
k8s运维之pod排错 K8S是一个开源的,用于管理云平台中多个主机上的容器化应用,Kubernetes的目标是让部署容器化变得简单并且高效 K8S的核心优势: 1,基于yaml文件实现容器的自动创建 ...
- 20_Vue如何监测数组类型数据发生改变的?
通过上一节,我们知道了vue检测对象数据发生改变的原理 但是还有个api我们没有讲解,Vue.set(): 这个API比较适合在理解了对象检测的原理后进行讲解 案例准备 html <!-- 创建 ...
- 网络协议之:redis protocol 详解
目录 简介 redis的高级用法 Redis中的pipline Redis中的Pub/Sub RESP protocol Simple Strings Bulk Strings RESP Intege ...
- 记录redis集群连接超时问题及解决方案
下午同事反馈,某业务场景性能测试过程中,出现异常,提供日志报: Redis command timed out 1. 先看下日志 org.springframework.dao.QueryTimeou ...
- 网页嵌入zabbix页面(不同域名)
先来结论: 方案一:绕过身份验证:https://www.cnblogs.com/JaSonS-toy/p/4939805.html(我不是这样实现,可以自行尝试) 方案二: 1.保证请求的ip与请求 ...
- Blazor和Vue对比学习(进阶.路由导航一):基本使用
Blazor和Vue都是单文件组件SPA,路由的实现逻辑非常相似,页面路径的改变都是组件的切换,但因为各自语言的特性,在实现方式上有较大差异. 一.安装 1.Vue:Router是Vue的一个插件.如 ...
- Kafka Connect学习
一.基础介绍 1.概念 2.Debezium 为捕获数据更改(change data capture,CDC)提供了一个低延迟的流式处理平台.可以消费数据库每一个行级别(row-level)的更改. ...