splay

SPLAY,LCT学习笔记(四)

ⅰ亾dé卋堺 提交于 2020-02-24 16:24:08
前三篇好像变成了SPLAY专题... 这一篇正式开始LCT! 其实LCT就是基于SPLAY的伸展操作维护树(森林)连通性的一个数据结构 核心操作有很多,我们以一道题为例: 例:bzoj 2049 洞穴勘测 要求:加边和删边,询问连通性 其实如果没有删边,裸跑并查集似乎就可以搞定 但由于存在删边,并查集思想受阻,我们要考虑更高级的数据结构 于是LCT横空出世! LCT的核心思想:多棵SPLAY维护虚实树链 首先介绍一下树链剖分问题: 树链剖分问题是将一棵树划分成多条树链的思想,有很多种剖分方法,比如轻重树链剖分(最常见,最常用),长短树链剖分(在部分题目中可以替代树上的dsu算法,而且无论码量还是时间复杂度都是很优越的),以及LCT要使用的虚实树链剖分 所谓虚实树链剖分,就是讲一棵树的边划分为实边和虚边,对于实边连起来的一条链用一个SPLAY维护 举个例子: 这是一棵树( 废话 ) 我们对他进行一下虚实剖分,如下: 如图,用实线表示的是实边,用虚线表示的是虚边,而我们用一些SPLAY维护这些链,就是: 如图,用黑色实线连起来的点表示一个SPLAY,用黑色虚线框圈起来的是一个SPLAY,用蓝色虚线连的边表示父亲指针 为什么要这样建立SPLAY? LCT维护树链的SPLAY有一个原则:每棵SPLAY的中序遍历会产生一个序列,而这个序列所对应的树链深度是单调递增的! 这是很重要的一个性质

与图论的邂逅04:LCT

不想你离开。 提交于 2020-02-24 16:23:01
  本着对数据结构这一块东西的一股兴趣,最近在集训的百忙之中抽空出来学LCT,终于学懂了这个高级玩意儿。 前置知识:Splay和树链剖分   Splay挺复杂的......这里就先不写,不然篇幅太大。树链剖分倒是可以大致地讲一下。 树链剖分   什么是树链剖分呢?就是把树给解剖成一条条的链子啦~那就先从最常用的重链剖分讲起。对于当前节点u和它的儿子构成的点集V,若size[v]=max{size[w] | w∈V},也就是以u的儿子为根的所有子树中size最大的那个儿子是v,那么称v是u的重儿子(定义size[x]:以x为根的子树所含有的点数)。如果这时把u向v连一条边,并且其它所有点也像这么做(连向它们的重儿子,叶子节点除外),由于重儿子唯一,父亲唯一,所以就会形成一条一条的链。这个链就叫重链。重链剖分是如此,类比一下就能够理解长链剖分:每个点连向子树深度最大的那个儿子。然而这两种剖法都不是LCT所使用的。LCT使用的是:实链剖分。   实链剖分就是说,把某个节点向它的某个儿子连实边,向其它儿子连虚边,这样也能连出一条条链子。实链剖分有什么好处呢?重链剖分和长链剖分中的size和dep都是固定的,也就是说剖完一棵树之后就无法再改变。而注意刚才实链剖分定义中的一句话:"把某个节点向它的某个儿子连实边",也就是说这个"某个儿子"是可以改变的

浅谈算法——splay

若如初见. 提交于 2020-02-21 07:26:17
BST(二叉查找树)是个有意思的东西,种类巨TM多,然后我们今天不讲其他的,我们今天就讲splay 首先,如果你不知道Splay是啥,你也得知道BST是啥 如上图就是一棵优美的BST,它对于每个点保证其左子树内所有点小于自己,右子树内所有点大于自己,而且这棵树高只有 \(\log n\) ,所以找一个点只需要 \(O(\log n)\) 的时间 但是如果这个图长得极端一点就会变成这样…… 这棵树就非常的不优美,每次查找的复杂度为 \(O(n)\) ,然后就 \(O(n^2)\) 了…… 然后各种大佬们为了解决这个蓝瘦的事情,纷纷想出了一些解决方案,其中有个叫Tarjan的大佬,弄出了一个名叫Splay的玩意,然后我们来讲一下Splay的一些操作 1.旋转 旋转式BST(Splay是其中的一种)基本上都有此操作,不然不叫作旋转式,像fhqtreap那种非旋转式BST则没有该操作。网上大部分将旋转分为两个,ZIG与ZAG 感觉这张图一点都不清楚。。。 其实是我懒得画一张了 左边到右边的是ZIG(x),右边到左边是ZAG(y) ZIG和ZAG的结合也有几种情况 你发现它们这样转来转去,这棵树依然满足BST性质的,而且上图ZIG-ZAG操作中,还减少了树的高度,所以旋转式BST就是基于ZIG,ZAG以及组合操作,通过不断旋转自身来保证其树高,使得其非常优美 但是,写4个旋转实在是太麻烦了

[2018.12.19]动态树LCT

戏子无情 提交于 2020-02-11 04:44:18
总算学会了...NOIp2018之前就开始学了...模板一直过不去... 需要先学会 Splay 。 现在切入正题。 先放模板题链接 LCT的概况 一种数据结构。(废话) 可以均摊 \(O(logn)\) 维护一个森林,支持的树上任意路径的查询,两棵树的连接、断开,单点的修改。 LCT的实现基于Splay。 大概长成下面这样↓ (图片来自 这位dalao的博客 ,下同) LCT中的边分为实边和虚边,每一个节点连向它的孩子的边中只有一条实边。 实边需要记录在这条边的两个端点上(即孩子记父亲,父亲记孩子),虚边只需要记录在孩子上(即孩子记父亲即可)。 其中实边(实线的边)连接的一组点构成一个Splay,大小依据为深度。 Splay的根的父亲指针指向这棵Splay中深度最小的节点在原树中的父亲(这是一条虚边)。 比如上面这个LCT的Splay长这样(当然和Splay的形态没关系)↓ 具体操作 LCT中的Splay必须维护的是翻转标记,其他的视题目而定。 首先是一个前置操作,判断点 \(y\) 是否是Splay的根(因为即使是Splay的根也可能有在别的Splay中有父亲,所以并不是没有父亲的才是根): bool Isroot(int x){ int y=t[x].f; return !(t[y].c[0]==x||t[y].c[1]==x); } 本文的Splay节点定义如下:

LCT小结

夙愿已清 提交于 2020-02-11 04:23:43
前言 去年其实已经学过 \(LCT\) 了 ,但因为准备 \(noip\) 就没做什么题,忘得差不多啦,来补份总结 其实 \(LCT\) 是可以用 \(FHQ\_treap\) 实现的,但似乎更慢??某 \(dalao\) 测试过的 反正 \(LCT\) 码量小(稍微压点就 \(60\) 行以下了),而且网上大部分 \(LCT\) 都是用 \(splay\) 操作的,暂时不建议大家用 \(FHQ\_treap\) 注:本文过于简短,只适合做总结观看 性质 \(LCT\) ,全称 \(Link Cut Tree\) ,是一种维护动态树的利器 \(1.\) 每个原树节点存在且仅存在于一棵 \(splay\) \(2.\) 整个 \(LCT\) ,是由多棵 \(splay\) 构成的森林,其中每棵 \(splay\) 维护的是一条从上至下在原树中深度递增的路径,中序遍历后的点序列的在原树中的深度递增 \(3.LCT\) 里的点靠实边和虚边连接,实边在 \(splay\) 中 虚边是由一棵 \(splay\) 指向另一棵 \(splay\) 的某个节点:该 \(splay\) 中的根指向原树深度最小的点的父亲(根与深度最小的点不等价) 当原树某点有多个儿子时,其中一个儿子存在于同棵 \(splay\) 且拉实边,其他儿子存在于其他的 \(splay\) 向该点拉虚边 \(Update(x)

LCT(Link Cut Tree)总结

淺唱寂寞╮ 提交于 2020-02-11 03:49:09
概念、性质简述 首先介绍一下 链剖分 的概念 链剖分,是指一类对树的边进行轻重划分的操作,这样做的目的是为了减少某些链上的修改、查询等操作的复杂度。 目前总共有三类:重链剖分,实链剖分和并不常见的长链剖分。 重链剖分 实际上我们经常讲的树剖,就是重链剖分的常用称呼。 对于每个点,选择最大的子树,将这条连边划分为重边,而连向其他子树的边划分为轻边。 若干重边连接在一起构成重链,用树状数组或线段树等静态数据结构维护。 这里就不赘述; 实链剖分 同样将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边。 区别在于虚实是可以动态变化的,因此要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链。 基于性质更加优秀的实链剖分,LCT(Link-Cut Tree)应运而生。 LCT维护的对象其实是一个森林。 在实链剖分的基础下,LCT资磁更多的操作 同样将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边。 区别在于虚实是可以动态变化的,因此要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链。 基于性质更加优秀的实链剖分,LCT(Link-Cut Tree)应运而生。 LCT维护的对象其实是一个森林。 在实链剖分的基础下,LCT资磁更多的操作 查询、修改链上的信息(最值,总和等) 随意指定原树的根(即换根) 动态连边、删边 合并两棵树、分离一棵树

链剖&LCT总结

醉酒当歌 提交于 2020-02-11 03:41:18
在搞LCT之前,我们不妨再看看喜闻乐见的树链剖分。 树链剖分有一道喜闻乐见的例题: NOI2015 软件包管理器 如果你看懂题目了,你就会明白它是叫你维护一个树,这棵树是不会动的,要兹磁子树求和,子树修改,树上路径求和,树上路径修改。 树链剖分就是把一个树剖分成像这样的东西: 一棵树用一坨重链组成,重链之间用轻链连接。 对于树上的每一个点,它和子树大小最大的那个的根节点在同一重链,其他儿子另成一条新重链。 这样可以证明每个点到根至多只有log级这么多段的连续的重链。 然后我们把连续的一坨重链用线段树维护一下。 代码(常数非常大 #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; #define SIZ 266666 namespace segt { #define LC(x) ((x)<<1) #define RC(x) (LC(x)+1) int M=131072,ls[SIZ],rs[SIZ],tag[SIZ],sum[SIZ]; inline bool avb(int x) { return 1<=x&&x<=2*M; } void pd(int x) { if(!avb(x)||tag[x]==-1) return; if(avb(LC(x))) tag[LC(x)]

Link-Cut-Tree

岁酱吖の 提交于 2020-02-11 03:32:39
模板题点这里 大体思路 可以看到, \(LCT\) 就是用于解决这一类问题的,下面我们就来看一下它是怎么实现的。 我们知道有一种叫做树剖的东西,这玩意儿好像可以支持链上的一些操作。 我们还知道有一种叫做 \(Splay\) 的东西,这玩意儿貌似可以可以通过瞎搞完成很多动态的操作。 要不? 让他们生个孩子?! XD 嗯反正大致思路就是这样子的啦! 一些定义 Preferred Child: 重儿子,重儿子与父亲节点在同一棵Splay中,一个节点最多只能有一个重儿子。 Preferred Edge: 重边,连接父亲节点和重儿子的边。 Preferred Path : 重链,由重边及重边连接的节点构成的链。 Auxiliary Tree(辅助树): 由一条重链上的所有节点所构成的 \(Splay\) 称作这条链的辅助树。 每个点的键值为这个点的深度,即这棵 \(Splay\) 的中序遍历是这条链从链顶到链底的所有节点构成的序列 辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点 (也就是说父亲不认轻儿子只认重儿子,儿子都认父亲) 具体实现 假如我们现在有两种操作: \(access(x)\) 可以让你把 \(x\) 到当前实树的根的路径全部变成重链,也就是说, \(x\) 到根路径上的所有点都会在一棵 \(Auxiliary Tree\) 中,而这可

蒟蒻林荫小复习——关于有限制区间元素查询的一些解法

我是研究僧i 提交于 2020-02-08 03:03:57
如题:本文主要说明对于区间有限制查询的一些解法(其实就两种) 问题1:给定一个数列,要求查询区间L—R中所有大于等于Va小于等于Vb的元素和 解法: 1.线段树套权值线段树 第一维维护区间,第二维作为权值线段树,维护值域在A—B之间的元素之和 每次查询就从第一维拉到对应区间,然后用Va和Vb确定在权值线段 树中的查询范围即可 2.分块 分块数组记为a,对每一个a块都开一个数组b,b数组将a块中元素拷贝后排序,新建c,对于每一个b都求前缀和 这样对于整块而言,用二分确定Va和Vb在b数组中的位置Ia,Ib,那么在这个数组的贡献即为c[lb]-c[la-1]. 零散部分暴力,不会超过2*sqrt(n) 问题2:在1的基础上求元素的个数 解法: 1.线段树套权值线段树 几乎同上,在第二维权值线段树中维护元素的个数即可(可能要离散化) 2.分块 分块数组记为a,对每一个a块都开一个权值数组b,b记录a中每种元素出现个数 b的范围为MinV—MaxV(离散化),对于每个b新开一个c记录b数组的前缀和 假设离散化后的Va,Vb为map[Va],map[Vb],则整块的贡献为c[map[Vb]]-c[map[Va]-1] 问题3:在1和2的基础上添加修改条件:可以是删去某一个元素(正常出题人应该不会这样搞),将某一个元素的值修改 解法: 1.线段树套权值线段树 大概方法同上

P3466 [POI2008]KLO-Building blocks(Splay)

喜夏-厌秋 提交于 2020-02-05 09:40:43
题意: N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务.你还要求输出结束状态时,每柱砖的高度 题解: 很显然,要让我们将这K个柱子变成一样高并且操作次数最少,就是求这K个数的中位数 所以我们需要用一种数据结构能够实现插入,删除,求第k大,它前面有多少个数,后面有多少个数。 然后根据这K个数和中位数就能计算出最少次数了 a n s = m i n ( ∑ i = l r ∣ a i − x ∣ ) ans = min(\sum_{i=l}^{r}|a_i-x|) a n s = m i n ( i = l ∑ r ​ ∣ a i ​ − x ∣ ) 找到中位数后就可以知道 a n s = m i n ( ∑ i = l m i d x − a i + ∑ i = m i d + 1 r a i − x ) ans = min(\sum_{i=l}^{mid}x-a_i+\sum_{i=mid+1}^{r}a_i-x) a n s = m i n ( i = l ∑ m i d ​ x − a i ​ + i = m i d + 1 ∑ r ​ a i ​ − x ) 左边可以通过将中位数伸展到根节点,然后求出比该数小的和及比它大的数的和即可