splay

列队

こ雲淡風輕ζ 提交于 2019-12-03 20:47:02
https://www.luogu.org/problem/P3960 splay 我们观察每一行除了最后一个数之外的数在操作中是如何变化的。 如果出队在(x,y),那么第x行(除了最后一个数)会弹出第y个数,它后面的数依次左移,再在最后插入一个数(就是最后一列当前的第x个数);然后,对于最后一列,我们要弹出第x个数(插入到第x行),再在最后插入一个数(就是刚出队的那个)。 所以,我们无论是对于行还是列,都要执行两种操作:第一,弹出第k个数,第二,在尾部插入一个数。这可以用splay来实现。 但是这样的话空间复杂度是 O(nm) O ( n m ) 。 我们发现有些人从始至终都是左右相邻的,这些人对应的splay节点可以合并。 于是我们在维护splay的时候,所有连续的数的区间我们用一个节点维护,记录这个节点表示的区间的左右端点; 如果我们发现要弹出的数在某个节点内部,我们就把它拆开,拆成3个节点(其中中间那个节点只有一个数,就是我们要的那个数),拆点的过程可以直接跑splay上的insert,删除中间那个节点就是splay上的删除。 总时间复杂度为 O(nlogn) O ( n l o g n ) 。 #include <cstdio> namespace rqy{ //命名空间是为了防止和某些奇怪的变量命名冲突,也可以去掉 typedef long long LL; const

Splay&LCT

跟風遠走 提交于 2019-12-03 14:37:23
Splay && LCT \(\text{Splay}\) 基本操作 1. \(Zig \& Zag\) 其思想是维护中序遍历不变 实现中我们不真的用 \(Zig\) 或 \(Zag\) 而是注意到他们调用的左右永远是反的 一个函数就可以实现,一定每次看图 inline void rotate(re int x){ re int y=fa[x],z=fa[y],l=*son[x]^x,r=l^1; if(fa[y])*son[z]==y?*son[z]=x:son[z][1]=x;fa[x]=z; son[y][l]=son[x][r];fa[son[y][l]]=y; son[x][r]=y;fa[y]=x; pushup(x);pushup(y); } \(2.Splay\) 有三种情况 \(Zig/Zag\) 和 \(Zig+Zig/Zag+Zag\) 和 \(Zig+Zag/Zag+Zig\) (永远从下往上) 记住一句话: 后面两个操作只有 \(x\) 与它父亲不共线就转它两次,否则转它一次 inline void Splay(re int x){ pushdown(x); while(fa[x]){ if(fa[fa[x]])pushdown(fa[fa[x]]);pushdown(fa[x]);pushdown(x); if(fa[fa[x]])((*son[fa

CF19D Points 平衡树

醉酒当歌 提交于 2019-12-03 05:13:26
没调完呢~ code: #include <set> #include <map> #include <cstdio> #include <algorithm> #define N 200005 #define inf 1200000005 #define lson p[x].ch[0] #define rson p[x].ch[1] #define setIO(s) freopen(s".in","r",stdin) using namespace std; int tot,cnt,pp,root; set<int>S[N]; map<int,int>idx,sp; set<int>::iterator it; struct data { int ch[2],f,maxx,val,id,size; }p[N]; int newnode() { return ++tot; } int get(int x) { return p[p[x].f].ch[1]==x; } void pushup(int x) { p[x].maxx=p[x].val; p[x].maxx=max(p[x].val,max(p[lson].maxx,p[rson].maxx)); p[x].size=p[lson].size+p[rson].size+1; } void rotate(int x) {

splay

匿名 (未验证) 提交于 2019-12-02 23:51:01
(又是一个被我咕咕咕很久了的知识点) 板子题 一、Splay 又叫伸展树,分裂树。是一种二叉排序/查找/搜索树。 Splay是一种平衡二叉树,及优化后的二叉查找树。靠伸展操作splay可以自我调整,使得提升效率 二、二叉排序树的性质 或者是一颗空树 或者具有以下性质: 若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值 若柚子树不空,则右子树上所有节点的值均大于或等于它的根节点的值 左右子树也分别为二叉排序树 同样的序列,因为排序不同,可能会生成不同的二叉排序树,查找效率性就不一定了。如果傻叉排序树退化成一条链,效率就很低。 三、变量定义 : : val[ x ]代表x存储的值 : cnt[ x ]代表x存储的重复权值的个数 : fa[ x ]代表x的父节点 : siz[ x ]代表x子树下的储存的权值数(包括重复权值,也包括自己) 四、支持的操作 查找第k大的数 查找一个数的排名 查找一个数的前驱 查找一个数的后继 区间翻转 --------------------------------------------------------------------------------- chk:查询一个节点是其父节点的左儿子还是右儿子 pushup:更新siz数组的值 rotate:旋转保持平衡 splay:伸展,将一个节点一直rotate到制定节点的儿子 find

4234最小差值生成树

匿名 (未验证) 提交于 2019-12-02 23:49:02
有点巧妙啊! s[x]每次维护的是最小值 我们将边按从大到小排个序,这样编号小的就在前面啦!QAQ 再按最小生成树的LCT的做法来 不过我们每次要用一个book标记前面最小边的编号 每次要更新答案时,一直往前跳,跳到最晚更新的即使最小的 我口胡的,错了请dalao指出 #include<iostream> #include<cmath> #include<cstring> #include<cstdio> #include<algorithm> #define MN 700005 #define re register int #define ll long long using namespace std; int f[MN],v[MN],s[MN],r[MN],son[MN][2]; int zhan[MN]; int n,m,cnt; int book[MN]; int ltk; struct tu{ int u,v,w; }e[MN]; bool comp(tu p,tu q){ return p.w<q.w; } int get(int x){////判断节点是否为一个Splay的根(与普通Splay的区别1) return son[f[x]][0]==x||son[f[x]][1]==x; }////如果连的是轻边,他的父亲的儿子里没有它 void pushup(int

文艺平衡树(Splay)题解

穿精又带淫゛_ 提交于 2019-12-02 23:47:28
题目描述: 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 。 输入: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n) m表示翻转操作次数 接下来m行每行两个数[l,r] 数据保证 1<=l<=r<=n 输出: 输出一行n个数字,表示原始序列经过m次变换后的结果 题目地址: bzoj 或 洛谷 题解: 这题目调了我好久,最后发现是全局变量的锅qwq。 最近刚学Splay,总算搞明白了Splay维护序列的原理。就来发题解记录一下。 首先我们的这一颗Splay的中序遍历即为我们要求的结果。这里的Splay并不是传统意义上的平衡二叉树(即某个节点的左儿子的权值小于该节点的权值,右儿子节点的权值大于该节点的权值)节点权值在这里除了最后输出是没有意义的。我们其实是用一个数在Splay中的排名来代表原序列上这个排名的所在位置的(注意是所在位置,并不代表某一特定值)。 然后我们考虑如何找出区间$[l,r]$,我们首先将排名为$l-1$的节点一路旋转到根,然后将排名为$r+1$的节点旋转为根节点的右儿子(此时它一定在根节点右子树中,因为排名比根节点的排名$l-1$更大)。然后此时根节点的右儿子的左子树内就是我们要求的区间$[l,r]

平衡树SPLAY

好久不见. 提交于 2019-12-02 16:51:04
平衡树SPLAY 平衡树这个东西 一点用没有 ,非常有用,而且锻炼码力,是个非常好的模板题! 那么SPLAY这个东西 十分的毒瘤 我只调了一上午就调出来了!(我真棒) 首先 我们要知道平衡树是一棵二叉查找树 他可以处理:加点,删点,前驱,后继,num的排名,某排名的num,合并两棵平衡树,分离两棵平衡树 关于其原理 网上有很多 而且比较简单 对于splay来说 原理并不是最难的 码代码才是最难的! 那么就提一提细节吧: 当这一棵树为空的时候, \(tot\) 不一定=0,而 \(root=0\) 当这一棵树为空的时候,如果新加入一个节点,那么没有必要对于这个节点进行splay操作,(反正我的代码会死循环QAQ) 在rotate完了时候 记得update呀 对于该有返回值的函数 一定要返回值!(可怜孩子调了半天) 还有就是在: \(insert(build),find,dele,oprank\) 函数里面要 \(splay\) 嗷 注意:在我的函数 \(rank\) 中 我只能找到在平衡树中有的 \(val\) 的排名 而不存在的 \(val\) 这个函数是查不到的 注释掉的代码也可以查询rank 但是 好丑啊!! #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> using

Splay的形象化理解

喜欢而已 提交于 2019-12-02 03:11:20
旋转: 让自己的k^1儿子代替自己成为父亲的儿子 代替父亲的位置成为爷爷的儿子 让父亲代替自己的k^1儿子成为自己的儿子 注意事项: 如果当前点和它的父节点是同向节点(都是左/右儿子),则先转父亲,再转自己;否则连续转两次自己 如果当前点的父亲是整棵树的树根,则直接转自己 来源: https://www.cnblogs.com/White-star/p/11728127.html

Splay 树

倖福魔咒の 提交于 2019-12-02 02:27:53
大佬链接: https://www.cnblogs.com/gzh-red/p/11011557.html #include <bits/stdc++.h> using namespace std; const int N=201000; struct splay_tree { int ff,cnt,ch[2],val,size; } t[N]; int root,tot; void update(int x) { t[x].size=t[t[x].ch[0]].size+t[t[x].ch[1]].size+t[x].cnt; } void rotate(int x) { int y=t[x].ff; int z=t[y].ff; int k=(t[y].ch[1]==x); t[z].ch[(t[z].ch[1]==y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; update(y);update(x); } void splay(int x,int s) { while(t[x].ff!=s) { int y=t[x].ff,z=t[y].ff; if (z!=s) (t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate

CF573E (平衡树)

怎甘沉沦 提交于 2019-12-01 20:16:56
CF573E 题意概要 给出一个长度为 \(n\) 的数列,从中选出一个子序列 \(b[1...m]\) ( 可以为空 ) 使得 \[ \sum_{i=1}^m{b_i*i}\] 最大,输出这个最大值。 其中 \(n\le10^5\) 题解 设 \(dp_{i,j}\) 表示前 \(i\) 个数选择 \(j\) 个数的最大值 那么,转移方程则为: \[ dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-1}+j*a_i) \] 于是我们就得到了一个 \(n^2\) 的做法 我们考虑优化这个式子。 经 \(dalao\) 证明,发现总有存在一个分界线,这之前的取前者,这之后的取后者 大佬的证明在 这里 我们二分分界线,然后用平衡树维护就好了 顺带一提,我今日方知 \(splay\) 没事多$ splay $几下还会变快 代码 #include<bits/stdc++.h> #include<windows.h> #define lch c[x][0] #define rch c[x][1] using namespace std; typedef long long ll; const int sz=1e5+7; int n; ll v,ans; int rt,cnt; int f[sz]; int c[sz][2]; int siz[sz]; ll val[sz]