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

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

不过还是自己心底一点 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. 基于RK3568 + FPGA国产平台的多通道AD实时采集显示方案分享

    在工业控制与数据采集领域,高精度的AD采集和实时显示至关重要.今天,我们就来基于瑞芯微RK3568J + FPGA国产平台深入探讨以下,它是如何实现该功能的.适用开发环境如下: Windows开发环境 ...

  2. 网络编程-关闭连接(1)-C/C++相关系统调用

    背景 在linux网络编程中,经常需要编写关闭socket的代码,比如心跳检测失败需要关闭重连:网络报异常需要关闭重连.但究竟关闭操作做了什么,却不太清楚.目前项目使用Netty框架来实现的网络编程, ...

  3. 探秘Transformer系列之(20)--- KV Cache

    探秘Transformer系列之(20)--- KV Cache 目录 探秘Transformer系列之(20)--- KV Cache 0x00 概述 0x01 自回归推理的问题 1.1 请求的生命 ...

  4. 【Guava】BiMap&Multimap&Multiset

    BiMap Map 可以实现 key -> value 的映射,如果想要 value -> key 的映射,就需要定义两个 Map,并且同步更新,很不优雅.Guava 提供了 BiMap ...

  5. TCP延迟调优之PSH参数与passt延迟问题修复

    qemu中使用passt来作为虚拟机的网卡NAT实现,希望能够利用它IP地址与host一致的优点.这本来是没有啥问题的,但是不知道为什么它的TCP入口流量的延迟很严重. 好吧,反正以后总是要改pass ...

  6. AI应用部署本地步骤

    训练 微调 Ollama Gpt-Sovits Stable-Diffusion

  7. 将Particle转成UGUI

    在unity官方论坛看到的一个解决方案,可以将Particle直接转换成CanvasRenderer元素显示.新建一个UIParticleSystem.cs脚本,将以下代码复制进去: using Un ...

  8. MySQL安装入门第一篇

    [1]MySQL的版本:近期主要历史版本有5.0/5.1/5.5/5.6/5.7,目前最新版本是MySQL8.6.0曾经是个内部试验版本,已取消了. MySQL8.0的版本历史 1) 2016-09- ...

  9. DPDI Online在线kettle调度工具

    1. DPDI简介 DPDI Online 是一款基于Kettle的强大在线任务调度平台,凭借其高效与灵活性,专为调度和监控Kettle客户端生成的ETL任务而设计 2. DPDI使用 2.1 DPD ...

  10. 基于CNN(卷积神经网络)的车牌号识别【结尾附完整项目下载地址】

    基于卷积神经网络(CNN)的车牌识别技术是一种深度学习方法,用于自动检测并识别车辆的车牌号码.以下是经过优化后的处理步骤: 图像预处理:首先对获取的车牌图像进行处理,包括将其转换为灰度图.二值化处理以 ...