启发式合并(堆、set、splay、treap)/线段树合并学习小记

启发式合并

  • 刚听到这个东西的时候,我是相当蒙圈的。特别是“启发式”这三个字莫名的装逼,因此之前一直没有学。
  • 实际上,这个东西就是一个SB贪心。
  • 以堆为例,若我们要合并两个堆a、b,我们有一种极其简单的做法:那就是比较一下它们的大小,将小的堆的每个元素依次插入到大的堆中。不妨设\(|a|≤|b|\),则时间复杂度即为:\(O(|a|*log_2(|a|+|b|))\)。
  • 这个东西看似很慢,但当点数较小的时候,我们可以证明复杂度是可被接受的。

  • 比如我们要合并n个堆,这n个堆共有m个点。设这n个堆\(=\{s_1,s_2,s_3,...,s_n\}\)。
  • 首先,我们合并\(s_1\)和\(s_2\),变成一个新的堆\(t_1\)。
  • 然后,我们合并\(t_1\)和\(s_3\),变成一个新的堆\(t_2\)。
  • ……
  • 以此类推,我们最终可以合并出一个堆\(t_{n-1}\)。

  • 合并堆a、b时,记1次操作为将a中的一个元素插入b(或将b中的一个元素插入a)。
  • 可以发现,第1次合并操作数\(≤|s_2|\),第2次合并操作数\(≤|s_3|\)……第i次合并操作数\(≤|s_{i+1}|\)。
  • 因此,总操作数\(≤\sum_{i=2}^n|s_i|≤m\)。而每次操作又是\(O(log_2m)\)的复杂度。因此:
  • 时间复杂度:\(O(n+mlog_2m)\)。

    推广

  • 启发式合并也可以用到set、splay、treap等平衡树上去。
  • 若我们要合并两棵平衡树a、b,也是先比较大小,将小的平衡树的每个元素依次插入大的平衡树。囿于插入的时间也是\(O(log_2n)\),因此总复杂度还是\(O(|a|*log_2(|a|+|b|))\)。
  • 注意:这里的合并并非treap的merge。merge(a,b)是强行让a所有元素的键值(要满足二叉排序树的性质的那个值)均小于b所有元素的键值,所以可以\(O(log_2n)\)做到;而这里要合并的两棵平衡树a、b的键值可能是交错不齐的。

    线段树合并

  • OI中常常遇到一些题目,要将若干物件不断合并,维护信息。
  • 如果合并的顺序不对,堆/平衡树的启发式合并会很慢。比如当你分治+启发式合并的时候,时间复杂度就变成\(O(n*(log_2n)^2)\)了。
  • 这个时候,就需要线段树合并。

  • 对于这个,相信大家都想得出下面这种合并步骤:
  • 为了方便确定一棵树是否为空,我们动态开点。
  • 比如,我们合并两棵权值线段树:
  • 显然,这么做的复杂度与两棵树公共的节点数成正比。
  • 但是,假设我们要合并多棵线段树呢?

  • 假设我们要合并n棵线段树,定义势能函数\(\Phi(n)\)为它们的节点个数和。
  • 每次合并线段树a、b时,设其公共点数为c,则合并后的\(\Phi(n)\)减少c,而时间复杂度增加c。
  • 因此,时间复杂度应≤节点个数和。
  • 当线段树中总共有m个元素时(比如n棵权值线段树,只存有m个数),每个元素都可以动态开辟至多\(log_2n\)个节点。因此,此时的时间复杂度应为\(O(n+mlog_2n)\)。
  • 注意:此时的时间复杂度并不受合并顺序的限制。换句话说,不论你按什么顺序合并,只要你是合并n棵只有m个元素的线段树,时间复杂度就是\(O(n+mlog_2n)\)。

    例题

    【BZOJ 2212】【Poi2011】 Tree Rotations
    【JZOJ5800】【洛谷P4416】 [COCI2017-2018#1] 被单

原文地址:https://www.cnblogs.com/Iking123/p/9484432.html

时间: 2024-08-04 18:39:12

启发式合并(堆、set、splay、treap)/线段树合并学习小记的相关文章

UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并

原文链接 www.cnblogs.com/zhouzhendong/p/UOJ400.html 前言 老年选手没有码力. 题解 先对第一棵树进行边分治,然后,设点 x 到分治中心的距离为 $D[x]$,点 x 在原树上的深度为 $d[x]$,那么 $$d[x]+d[y] - d[LCA(x,y)] - d'[LCA(x,y)] = \frac 12(D[x] + d[x]) + \frac 12 (D[y] + d[y]) - d'[LCA(x,y)]$$ 于是我们考虑将分治区域内的节点在第二棵

启发式合并&线段树合并&treap合并&splay合并

启发式合并 有\(n\)个集合,每次让你合并两个集合,或询问一个集合中是否存在某个元素. ? 我们可以用平衡树/set维护集合. ? 对于合并两个\(A,B\),如果\(|A|<|B|\),那么我们就把\(A\)中的每个元素暴力加到\(B\)中,否则就把\(B\)中的元素暴力加到\(A\)中. ? 对于一次把\(A\)中的每个元素暴力加到\(B\)中的操作,\(|A|\)会变成\(|A|+|B|\),也就是说大小至少会翻倍,所以一个元素最多被暴力插入\(\log n\)次.每次插入的时间复杂度是

【BZOJ2733】永无乡[splay启发式合并or线段树合并]

题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被这坑了. 网址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 这道题似乎挺经典的(至少我看许多神犇很早就做了这道题).这道题有两种写法:并查集+(splay启发式合并or线段树合并).我写的是线段树合并,因为--splay不会打+懒得学.

bzoj2733: [HNOI2012]永无乡(splay+启发式合并/线段树合并)

这题之前写过线段树合并,今天复习Splay的时候想起这题,打算写一次Splay+启发式合并. 好爽!!! 写了长长的代码(其实也不长),只凭着下午的一点记忆(没背板子...),调了好久好久,过了样例,submit,1A! 哇真的舒服 调试输出懒得删了QwQ #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> #include

【BZOJ4919】[Lydsy六月月赛]大根堆 线段树合并

[BZOJ4919][Lydsy六月月赛]大根堆 Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么v_i>v_j. 请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树. Input 第一行包含一个正整数n(1<=n<=200000),表示节点的个数. 接下来n行,每行两个整数v

HDU - 4358 Boring counting (树上启发式合并/线段树合并)

题目链接 题意:统计树上每个结点中恰好出现了k次的颜色数. dsu on tree/线段树合并裸题. 启发式合并1:(748ms) 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10; 5 int n,m,k,a[N],b[N],nb,fa[N],son[N],siz[N],cnt[N],ans[N],now,ne,hd[N],ka; 6 struct E {

luoguP3359 改造异或树 线段树合并

删边转化为加边 然后每次用线段树合并就行..... 确确实实很简单 然而为什么线段树合并跑不过$splay$的启发式合并,常数稍大了点... 复杂度$O(n \log n)$ #include <vector> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> namespace remoon { #define ri register in

【BZOJ4399】魔法少女LJJ 线段树合并

[BZOJ4399]魔法少女LJJ Description 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味:小猴在枝头悠来荡去,好不自在:各式各样的鲜花争相开放,各种树枝的枝头挂满沉甸甸的野果:鸟儿的歌声婉转动听,小河里飘着落下的花瓣真是人间仙境”SHY觉得LJJ还是太naive,一天,SHY带着自己心爱的图找到LJJ,对LJJ说:“既然你已经见识过动态树

2017多校第8场 HDU 6133 Army Formations 线段树合并

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6133 题意:给你一棵n个节点的二叉树,每个节点有一个提交任务的时间,每个节点总的提交任务的罚时为:提交这个节点和其子树所有的任务,每个任务提交时间的总和为该点的罚时.求每个节点提交完所有任务的最小罚时. 解法:根据题意,我们可以知道每个节点的提交的最小罚时为,按照任务的提交时间从小到大的来提交任务,可以得到最小的罚时.所以我们可以用线段树合并,先建立权值线段树,记录权值区间L到R的所有权值sum与s