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

感觉有的时候已经无法与人正常交流,净做唐事、说些唐话了。或许是我无法准确猜到别人喜欢什么吧。

不过还是自己心底一点 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;
}
推图

Reference

被生命所厌恶。 - 百度百科

并查集 - OI Wiki

并查集复杂度 - OI Wiki

时间复杂度-势能分析浅谈 - 洛谷专栏

从理论分析并查集的时间复杂度 - 洛谷专栏

题解 P3402 【【模板】可持久化并查集】 - 洛谷专栏

可持久化并查集_可持化并查集-CSDN博客

可持久化并查集 - storms11 - 博客园

【闲话 No.3】 并查集相关的更多相关文章

  1. PAT甲级 并查集 相关题_C++题解

    并查集 PAT (Advanced Level) Practice 并查集 相关题 <算法笔记> 重点摘要 1034 Head of a Gang (30) 1107 Social Clu ...

  2. K:Union-Find(并查集)算法

    相关介绍:  并查集的相关算法,是我见过的,最为之有趣的算法之一.并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.其相关的实现代码较为简短,实现思想也 ...

  3. poj1182-食物链-带权并查集-种类并查集

    (这应该是我写的第一个和带权并查集相关的题,还不是很了解,所以这篇博客我以后还会做修改,力求更号理解! 题意和思路: 中文题意,我简单提一下: A->B,B->C,C->A.A吃B, ...

  4. 并查集——合作网络D306

    合作网络D306             运行时间限制:1000ms: 运行空间限制:51200KB: 试题描述 有n个结点,初始时每个结点的父结点都不存在.你的任务是执行若干次Set操作和Query ...

  5. 【进阶——种类并查集】hdu 1829 A Bug's Life (基础种类并查集)TUD Programming Contest 2005, Darmstadt, Germany

    先说说种类并查集吧. 种类并查集是并查集的一种.但是,种类并查集中的数据是分若干类的.具体属于哪一类,有多少类,都要视具体情况而定.当然属于哪一类,要再开一个数组来储存.所以,种类并查集一般有两个数组 ...

  6. 并查集(Union-Find)算法介绍

    原文链接:http://blog.csdn.net/dm_vincent/article/details/7655764 本文主要介绍解决动态连通性一类问题的一种算法,使用到了一种叫做并查集的数据结构 ...

  7. stl 和并查集应用

    抱歉这么久才写出一篇文章,最近进度有点慢.这么慢是有原因的,我在想如何改进能让大家看系列文章的时候更方便一些,现在这个问题有了答案,在以后的推送中,我将尽量把例题和相关知识点在同一天推出,其次在代码分 ...

  8. 并查集(Java实现)

    (最好在电脑下浏览本篇博客...手机上看代码不方便) 当时学的时候看的一本印度的数据结构书(好像是..有点忘了..反正跟同学们看的都不一样...)...里面把本文提到的所有情况都提到了,我这里只是重复 ...

  9. 数据结构 之 并查集(Disjoint Set)

    一.并查集的概念:     首先,为了引出并查集,先介绍几个概念:     1.等价关系(Equivalent Relation)     自反性.对称性.传递性.     如果a和b存在等价关系,记 ...

  10. 关于最小生成树(并查集)prime和kruskal

    适合对并查集有一定理解的人.  新手可能看不懂吧.... 并查集简单点说就是将相关的2个数字联系起来 比如 房子                      1   2    3   4  5   6 ...

随机推荐

  1. 【Java】字符串常用操作

    字符的常用技巧 char c; 字母的顺序:c - 'A'.c - 'a' 字母大小写转换:c - 'a' + 'A'.c - 'A' + 'a' 数字字符转换为数字:c - '0' String.S ...

  2. 【Ubuntu】安装OpenSSH启用远程连接

    [Ubuntu]安装OpenSSH启用远程连接 零.安装软件 使用如下代码安装OpenSSH服务端: sudo apt install openssh-server 壹.启动服务 使用如下代码启动Op ...

  3. AnnotationAwareAspectJAutoProxyCreator后置处理器的BeanDefinition定义信息导入和其对象实例创建过程

    步骤1 我们从配置类上的@EnableAspectJAutoProxy 注解入手,进入发现这个注解上又有一个@Import(AspectJAutoProxyRegistrar.class)注解, 了解 ...

  4. Asp.net mvc基础(一):Razor语法

    1.使用@{C#代码区域},调用@C#代码 2.使用@调用foreach,for,if等语句 2.在foreach,for,if等语句中使用汉字会报错,原因是在代码中纯文字会被认为是C#代码 如下: ...

  5. memcached DRDOS攻击实验

    memcached DRDOS攻击实验 一.前提 关于drdos DRDoS(Distributed Reflection Denial of Service) 指的是利用IP Spoofing技术, ...

  6. Argo CD

    目录 一.什么是 Argo CD 二.为什么选择 Argo CD 三.Argo CD 架构 1.API服务器 2.存储库服务器 3.应用程序控制器 四.Argo CD 的使用 1.要求 2.安装 Ar ...

  7. configfs-用户空间控制的内核对象配置

    1. 什么是configfs? configfs 是一个基于内存的文件系统,它提供了与sysfs相反的功能.sysfs 是一个基于文件系统的内核对象视图,而configfs 是一个基于文件系统的内核对 ...

  8. k8s-1.18.0版本-kubeadmin部署(提供阿里云镜像)(二)master节点

    k8s-1.18.0版本-kubeadmin部署 (提供阿里云镜像) 个人服务器地址:http://101.201.140.7/wp-blog/ 系统开启kube-proxy的ipvs前置条件 从k8 ...

  9. WPF初学者的一点迷思

    1.WPF只是前端!前端!前端!看了两天的视频,跟着敲了三个项目,自己写了一个小demo之后,从gitee上下了一个别的的框架之后才整明白,WPF只是前端.或者说只是把原本winfrom的界面+事件+ ...

  10. Dify+DeepSeek实战教程!企业级 AI 文档库本地化部署,数据安全与智能检索我都要

    上次折腾完 DeepSeek 的本地私有化部署后,心里就一直琢磨着:能不能给咱们 Rainbond 的用户再做点实用的东西?毕竟平时总收到反馈说文档查找不够方便,要是能有个 AI 文档助手该多好.正想 ...