【闲话 No.3】 并查集相关

命に嫌われている。
小唐话

感觉有的时候已经无法与人正常交流,净做唐事、说些唐话了。或许是我无法准确猜到别人喜欢什么吧。
不过还是自己心底一点 joker 之心/私心作祟吧。
感觉我有时做的唐事真的挺对不起大家的。
可我又怎么战胜心底的那些东西,抑或是弥补这些呢?

「死にたいなんて言うなよ。」
「諦めないで生きろよ。」
そんな歌が正しいなんて馬鹿げてるよな。
実際自分は死んでもよくて
周りが死んだら悲しくて
「それが嫌だから」っていうエゴなんです。
他人が生きてもどうでもよくて
誰かを嫌うこともファッションで
それでも「平和に生きよう」なんて
素敵なことでしょう。
画面の先では誰かが死んで
それを嘆いて誰かが歌って
それに感化された少年がナイフを持って走った。
僕らは命に嫌われている。
価値観もエゴも押し付けて
いつも誰かを殺したい歌を
簡単に電波で流した。
僕らは命に嫌われている。
軽々しく死にたいだとか
軽々しく命を見てる 僕らは命に嫌われている。
お金がないので今日も 一日中惰眠を謳歌する。
生きる意味なんて見出せず、
無駄を自覚して息をする。
「寂しい」なんて言葉でこの傷が表せていいものか
そんな意地ばかり抱え今日も一人ベッドに眠る
少年だった僕たちはいつか青年に変わってく。
年老いていつか枯れ葉のように
誰にも知られず朽ちていく。
不死身の身体を手に入れて、
一生死なずに生きていく。
そんなSFを妄想してる
自分が死んでもどうでもよくて
それでも周りに生きて欲しくて
矛盾を抱えて生きてくなんて怒られてしまう。
「正しいものは正しくいなさい。」
「死にたくないなら生きていなさい。」
悲しくなるならそれでもいいなら
ずっと一人で笑えよ。
僕らは命に嫌われている。
幸福の意味すらわからず
生まれた環境ばかり憎んで
簡単に過去ばかり呪う。
僕らは命に嫌われている。
さよならばかりが好きすぎて
本当の別れなど知らない 僕らは命に嫌われている。
幸福も別れも愛情も友情も
滑稽な夢の戯れで全部カネで買える代物。
明日死んでしまうかもしれない。
すべて無駄になるかもしれない。
朝も 夜も 春も 秋も
変わらず誰かがどこかで死ぬ。
夢も明日も何もいらない。
君が生きていたならそれでいい。
そうだ。本当はそういうことが歌いたい。
命に嫌われている。
結局いつかは死んでいく。
君だって僕だっていつかは枯れ葉のように朽ちてく。
それでも僕らは必死に生きて
命を必死に抱えて生きて
殺して、足掻いて、笑って、抱えて
生きて、生きて、生きて、生きて、生きろ。

温馨提示

由于一些尚未查明的问题,渲染公式时有小概率导致错位,如果您遇到类似情况,请尝试稍等片刻或者刷新。
对您阅读造成的不便,笔者深表歉意。

通用规定

在并查集上,我们定义 \(fa_x\) 为节点 \(x\) 的父亲,\(rank_x\) 为节点 \(x\) 的秩。最开始时,\(rank_x=0\),当对点 \(x,y(rank_x\leq{rank_y})\) 发生按秩合并时,若 \(rank_x < rank_y\),则两点的秩均不改变。否则 \(rank_y=rank_x+1\),并使 \(y\) 成为 \(x\) 的祖先。可以发现,\(rank_x+1\leq{rank_{fa_x}}\)。可以发现秩与树高同阶,且每个节点的秩单调不减。
我们还定义 \(root_x\) 为并查集上 \(x\) 所在集合的根节点,\(size_x\) 为以 \(x\) 为根的子树大小。
除此之外,我们定义对于一个函数 \(f\)\(f^k\) 表示将 \(f\) 复合 \(k\) 次。例如 \(f^4(2)=f\left(f\left(f\left(f\left(2\right)\right)\right)\right)\)
我们规定合并两个\(x,y\) 的集合为 \(\operatorname{merge}(x,y)\),查找 \(root_x\)\(\operatorname{find}(x)\)\(n\) 为一个与元素个数、操作次数均同阶的量。
我们规定对于任意一个点 \(x\) 若它是根节点或 \(rank_x=0\),那么 \(\operatorname{g}(x)=0\),反之则为 \(1\)

并查集按秩合并复杂度分析

我们发现 \(\operatorname{merge}(x,y)\)\(O(1)\) 的,只要证明 \(\operatorname{find}(x)\)\(O(\log{n})\) 的,即树高最大为 \(O(\log{n})\),即可证明总的复杂度为 \(O(n\log{n})\)
当我们按照上文所述的秩合并时,设 \(\operatorname{maxsize}(i)\) 表示秩为 \(i\) 的最小集合的大小。考虑得到第一个秩为 \(i+1\) 的集合至少需要合并两个秩为 \(i\) 的集合,所以 \(\operatorname{maxsize}(i+1)\geq{2\operatorname{maxsize}(i)}\)。由于这里的秩代表的正是树高,所以树高最多为 \(O(\log{n})\)
当我们按照集合大小合并(类似“启发式合并”)时,对于任意 \(y=fa_x\),在合并前 \(size_y\geq{size_x}\),合并之后 \(size_y\geq{2size_x}\),所以从叶子每向根的方向跳一条边,那么子树大小就会翻倍,所以跳的次数,也就是树高至多为 \(O(\log{n})\)

并查集路径压缩和按秩合并复杂度分析

我们定义阿克曼函数 \(\operatorname{A}_k(x)\)

\[\operatorname{A}_k(x)=\begin{cases}x+1&(k=0) \\\operatorname{A}^{x+1}_{k-1}(x)&(k\neq{0})\end{cases} \]

以及反阿克曼函数 \(\operatorname{\alpha}(x)\) 表示使 \(\operatorname{A}_y(1)\geq{x}\) 的值 \(y\)。对于任意一个 \(\operatorname{g}(x)=1\) 的点 \(x\),我们定义 \(\operatorname{k}(x)\) 为使 \(rank_{fa_x}\geq{\operatorname{A}_y(rank_x)}\) 最大的 \(y\)\(\operatorname{c}(x)\) 为使 \(rank_{fa_x}\geq{\operatorname{A}^y_{\operatorname{k}(x)}(rank_x)}\) 最大的 \(y\)。换言之, \(\operatorname{k}(x)\) 表示展开 \(\operatorname{A}(x)\) 的最大层数,\(\operatorname{c}(x)\) 为在最大层数的基础上展开的最大次数(类似 \(\operatorname{c}(x)a^{\operatorname{k}(x)}\),其中乘法运算为展开阿克曼函数)。显然当 \(rank_{fa_x}\) 增大,要么 \(\operatorname{k}(x)\) 不变 \(\operatorname{c}(x)\) 变大或不变,要么前者改变,后者可能变大、变小或不变。可以发现,由于 \(rank_x+1\leq{rank_{fa_x}}\),当 \(\operatorname{g}(x)=1\) 时至少有 \(rank_{fa_x}\geq{\operatorname{A}^1_0(rank_x)}\),以及 \(rank_{fa_x}\) 最大为最大树高减 \(1\),即元素个数减 \(1\),所以 \(0\leq{\operatorname{k}(x)} < \operatorname{\alpha}(n)\)。而且由于当 \(\operatorname{c}(x)\geq{rank_x+1}\) 时,可以抽出其中 \(rank_x+1\) 使得 \(\operatorname{k}\) 值加以,与其最大性矛盾,所以 \(0 < \operatorname{c}(x) \leq {rank_x}\)
我们定义势能函数 \(\operatorname{\Phi}(x)\)

\[\operatorname{\Phi}(x)=\begin{cases}rank_x\operatorname{\alpha}(n)&(\operatorname{g}(x)=0) \\rank_x(\operatorname{\alpha}(n)-\operatorname{k}(x))-\operatorname{c}(x)&(\operatorname{g}(x)=1)\end{cases} \]

\(\operatorname{\Phi}\) 表示所有点的势能总和。下文中为了直观,笔者可能称"势能总和"为“势能池”(根据上文不等关系容易发现势能函数一定为正)。
接下来,我们从并查集的两种操作分别证明总复杂度为 \(O(n\operatorname{\alpha}(n))\)

merge 操作

不考虑之前将两个节点跳到根上的操作,该操作的复杂度为 \(O(1)\),所以我们只看势能池的变化。假设合并的两个节点分别为 \(x,y\),且 \(rank_x\leq{rank_y}\)。考虑势能变化的节点一定是 \(x,y\) 以及 \(y\) 的儿子(当 \(rank_y\) 增加时)。
首先考虑点 \(x\),它从根节点转向儿子节点, \(\operatorname{k}\) 值和 \(\operatorname{c}\) 值一定被附上了一个非负的值,显然这只能导致势能函数降低。
对于 \(y\) 的儿子,当 \(\operatorname{k}\) 值增加时,会至少增加 \(1\) 使势能函数减少该点的秩,而 \(\operatorname{c}\) 值一定小于等于该点的秩,所以他的减小至多会使势能函数增加该点的秩减 \(1\)(函数值非负),也就是说势能函数不会因此增加,而且以此类推,当 \(\operatorname{k}\)\(\operatorname{c}\) 增大时,势能函数至少减 \(1\)(这个结论后面会用)。当 \(\operatorname{k}\) 值不变时,\(\operatorname{c}\) 值不会减小,势能函数也不会增加。由于 \(rank_y\) 增大,所以 \(\operatorname{k}\) 不减。综上,这些儿子的势能函数不会增大。
而对于点 \(y\) 他的秩至多增大 \(1\),势能总和至多增大 \(\operatorname{\alpha}(n)\),因此我们证明了势能池最多经历过 \(O(n\operatorname{\alpha}(n))\) 个单位的势能的出入。如果查找操作不增大势能总和,且每次查找操作中与势能总和无关的操作的复杂度最多为 \(O(\operatorname{\alpha}(n))\),即可证明并查集的复杂度。

find 操作

先来考虑势能变化。考虑每个节点的秩不变,所以势能函数变化只由 \(\operatorname{k}\)\(\operatorname{c}\) 增加引起。由于每个节点只会被重连到秩更大的节点上,所以两函数值只增不减,与上文证明 \(y\) 的儿子势能不增同理,势能总和只减不增。我们发现若要势能总和发生变化,当且仅当 \(\operatorname{c}\)\(\operatorname{k}\) 至少一者增加,即阿克曼函数至少多以 \(\operatorname{k}(x)\) 为底打开一次,所以对于势能发生变化的点(显然父亲被转到根上) \(rank_{root_x}\geq{\operatorname{A}_{\operatorname{k}(x)}\left(\operatorname{A}^{\operatorname{c}(x)}_{\operatorname{k}(x)}(rank_x)\right)}\)
我们发现,假设查询路径上有一对点 \(x,y\) 满足 \(y\)\(x\) 的祖先,且 \(\operatorname{k}(x)=\operatorname{k}(y)-p=d(p\geq{0})\),那么有 \(rank_{root_x}\geq{\operatorname{A}^{\operatorname{c}(y)}_{d+p}(rank_y)}\geq{\operatorname{A}_{d}(rank_y)}\) 以及 \(rank_y\geq{\operatorname{A}^{\operatorname{c}(x)}_{d}(rank_x)}\)。将后式中的 \(rank_y\) 换入前式,可得 \(rank_{root_x}\geq{\operatorname{A}^{\operatorname{c}(y)}_{d+p}\left(\operatorname{A}^{\operatorname{c}(x)}_{d}(rank_x)\right)}\geq{\operatorname{A}_{d}\left(\operatorname{A}^{\operatorname{c}(x)}_{d}(rank_x)\right)}\)。由此得出,所有可以找到满足这样条件的 \(y\)\(x\) 都会引起势能至少减少 \(1\),他们造成的复杂度消耗由势能池大小限制,所以可以不用考虑。而对于其他的点,至多会有树根,查找经过的树根儿子,以及后面找不到这样 \(y\)\(x\) 点。可以发现,最后一部分最多只会在节点 \(\operatorname{k}\) 单减且每一种取值都出现时卡到最大(每一种取值的最靠近根的节点),所以总共最多有 \(O(\operatorname{\alpha}(n))\) 个这样的节点。

综上,并查集的复杂度得证。

关于按集合大小合并的复杂度正确性证明

我们发现,将证明中的 \(rank\) 改为 \(size\) 只会影响我们用到 \(rank\) 性质的地方,分别是:

  1. 在要求 \(\operatorname{k}(x) < \operatorname{\alpha}(n)\) 时要求 \(rank_x\) 最大为元素个数减 \(1\)
  2. 在证明势能池最大出入总和时,要求 \(rank\) 值经过一次 \(\operatorname{merge}\) 至多增加 \(1\)
  3. \(\operatorname{merge}\) 操作使秩变大,其余操作均不会改变秩。
  4. \(\operatorname{find}\) 操作会将节点连到秩更大的节点,也就是说,根节点秩大于集合内其他节点的秩。

我们发现,对于每个节点 \(x\)\(\log_2{size_x}\) 也满足以上性质。

  1. 在一般情况下显然 \(\log_2{n}\leq{n-1}\),除非 \(n\) 小到算法退化到指数级(?)也可以接受的程度。
  2. 每次 \(\operatorname{merge}(x,y)(size_x\leq{size_y})\),有 \(2size_y\geq{size_y+size_x}\),所以 \(\log_2{size_y}\) 至多加 \(1\)
  3. 只有 \(\operatorname{merge}\) 会使 \(size\) 变大。
  4. 根节点 \(size\) 必定大于后代。

总上,我们可以使用 \(\log_2{size_x}\) 作秩,所以可以用 \(size_x\) 作秩,也就是按集合大小合并。

为什么纯路径压缩复杂度不正确

因为每次 \(\operatorname{merge}(x,y)(rank_x\leq{rank_y})\) 使 \(rank_y=\max(rank_y,rank_x+1)\),可能使 \(rank_y\) 增加 \(1\) 以上导致无法保证势能函数每次最多增加 \(O(\operatorname{\alpha}(n))\)
具体卡法可见 OI Wiki。

可持久化并查集

用可持久化数组维护 \(fa_x\)\(rank_x\) 即可。由于每通过 \(\operatorname{merge}\) 新建一个版本可能会使势能总和成倍增加(相当于复制),所以路径压缩不能将其优化到 \(O(n\log{n}\operatorname{\alpha}(n))\),所以我们只用按秩合并,最终复杂度为 \(O(n\log^2{n})\)

Code
#include<bits/stdc++.h>
using namespace std;
int n,m,scnt,cur;
struct segt{struct node{int data,lson,rson;}data[8000100];int cnt,root[200100];void build(int &now,int lft,int rgt,int* dt){now=++cnt;if(lft==rgt){data[now].data=dt[lft];return;}int mid=(lft+rgt)>>1;build(data[now].lson,lft,mid,dt);build(data[now].rson,mid+1,rgt,dt);}void insert(int &now,int old,int lft,int rgt,int pos,int dt){now=++cnt;if(lft==rgt){data[now].data=dt;return;}int mid=(lft+rgt)>>1;if(pos<=mid){data[now].rson=data[old].rson;insert(data[now].lson,data[old].lson,lft,mid,pos,dt);}else{data[now].lson=data[old].lson;insert(data[now].rson,data[old].rson,mid+1,rgt,pos,dt);}}inline int query(int now,int x){int lft=1,rgt=n;for(;lft^rgt;){int mid=(lft+rgt)>>1;if(x<=mid) now=data[now].lson,rgt=mid;else now=data[now].rson,lft=mid+1;}return data[now].data;}
}trfa,trrk;
inline int find(int x){for(;;){int tem=trfa.query(trfa.root[cur],x);if(tem^x) x=tem;else return x;}
}
inline void merge(int x,int y){x=find(x);y=find(y);int rx=trrk.query(trrk.root[cur],x),ry=trrk.query(trrk.root[cur],y);if(rx>ry) swap(x,y),swap(rx,ry);trfa.insert(trfa.root[++scnt],trfa.root[cur],1,n,x,y);trrk.insert(trrk.root[scnt],trrk.root[cur],1,n,y,max(rx+1,ry));cur=scnt;
}
int a[100100],b[100100];
int main(){scanf("%d%d",&n,&m);trrk.build(trrk.root[0],1,n,a);for(int i=1;i<=n;++i){a[i]=i;}trfa.build(trfa.root[0],1,n,a);int opt,ta,tb;for(int i=1;i<=m;++i){scanf("%d%d",&opt,&ta);if(opt==1){scanf("%d",&tb);merge(ta,tb);}else if(opt^3){cur=b[ta];}else{scanf("%d",&tb);ta=find(ta);tb=find(tb);if(ta^tb) printf("0\n",ta,tb);else printf("1\n");}b[i]=cur;}return 0;
}
推图

image

Reference

被生命所厌恶。 - 百度百科
并查集 - OI Wiki
并查集复杂度 - OI Wiki
时间复杂度-势能分析浅谈 - 洛谷专栏
从理论分析并查集的时间复杂度 - 洛谷专栏
题解 P3402 【【模板】可持久化并查集】 - 洛谷专栏
可持久化并查集_可持化并查集-CSDN博客
可持久化并查集 - storms11 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/907871.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.29 学习记录

实现了科技页面的树状图查询和分页查询

Django - admin djangoql

效果: 1. pip installpip install djangoql 2. settings.pyINSTALLED_APPS = [djangoql, ] 3. admin.pyfrom django.contrib import admin from djangoql.admin import DjangoQLSearchMixinfrom .models import *@admin.register(User) class UserAdmin(DjangoQLSearchMixi…

[rCore学习笔记 025 extend] 带优先级的抢占式调度

引言 因为rcore并非设计为一个rtos,而是在我们需要的时候我们需要在设计的时候考虑到线程切换的时候的延时问题. 回顾上一部分的使用环形队列进行调度的方式,我们会发现我们寻找下一个Ready的任务的时间是不均匀的.并且我们的任务是没有优先级的,可以认为是平权的,因此,为了:快…

第五周第三章3.6-3.8,思考与练习3.19-3.21

3.6 import time a = "strating" print("{:^}".format(a),end=) for i in range(11): s = . * i print("{:<}".format(s),end=) time.sleep(1) print("Done!") 思考与练习3.19 import time current_time = time.time() time_tuple …

【Java - demo】Redis开发入门

Redis 是一个高性能的键值存储数据库,常用于缓存、消息队列等场景。 本文将以简单易懂的方式介绍 Redis 的基本概念和使用方法,并附上 Java 示例代码,帮助你快速上手。 一、Redis 是什么? Redis(Remote Dictionary Server)是一个开源的、基于内存的键值存储系统。它支持多…

【流量预警】装了UU远程/GameViewer的小心了

前言 月末了,没流量了,不过我今天怎么跑了4个G的流量? 我倒要看看是谁干的好事。热点跑的?但是我一整天都挂着手机连电脑没动啊,最多也就偶尔用了一下。 别让我找到是谁偷跑我流量跑这么多 好啊好,UU远程,得,你清高,你偷偷上传我电脑数据是吧。虽说没抓到现行也只能怀…

注释、关键字、标识符

1、Java的注释有几种? 单行注释 使用双斜杠//,仅注释该行//后的内容。 多行注释(块注释) 使用/*开始,*/结束,中间所有内容均为注释,可跨越多行。 文档注释 使用/**开始,*/结束,通常用于生成API文档(通过Javadoc工具)。支持特定标签(如@param、@return等)。 关键区…

Mysql全量安装配置教程(超详细window版本无需配置环境变量)尊享篇

官网下载注册 注册 官网地址:https://www.oracle.com/mysql/technologies/mysql-enterprise-edition-downloads.html#windows 下载需要注册登录 注册可以用临时邮箱地址:https://www.suiyongsuiqi.com/zh/mail/ 如果显示400badrequest 打开无痕浏览窗口重新访问即可 解压所需…

Linux基本命令-1

Linux路径的描述方式Windows中,以\表示层级关系.有C盘,D盘等多个根目录.Linux中,以/表示层级关系./为根目录.eg:/usr/local/hello.txtLinux基础命令 命令的通用格式:命令+[选项]+[参数],其中用[ ]选中的选项,参数表示可省略. eg: ls -l /home/hu ==> 以列表形…

[Vue] Vue 模板编译原理解析 part 2

转换器 主要的目的是将模板的 AST 转换为 JS 的 AST,整个模板的编译过程如下: // Vue 的模板编译器 function compile(template) {// 1. 得到模板的 ASTconst ast = parse(template);// 2. 将模板 AST 转为 JS ASTtransform(ast); }整个转换实际上可以分为两个大的部分:模板…

Flasher V5 JLink Pro V6

原帖链接:https://nicemcu.github.io/2025/03/29/PSoC4/FlasherV5/ 2025年3月29日,神变月最后一天,我们参加了放生~ 在这个特殊的日子里,我完成了Flasher V5和JLink Pro V6的crack,难掩内心的喜悦与激动,记录下这一时刻。 前不久小黄鱼上收了一枚Flasher ARM,硬件版本V5…