平衡树(splay)

平衡数模板

平衡树能干些什么呢?

  • 插入一个数

  • 删除一个数

  • 查询数 x x x的排名(小于x" role="presentation" style="position: relative;">xxx的元素个数)

  • 查询排名为 x x x的数

  • 查询x" role="presentation" style="position: relative;">xxx的前驱(小于 x x x且最大的数)

  • 查询x" role="presentation" style="position: relative;">xxx的后继(大于 x x x且最小的数)

  • 区间翻转

  • 区间加乘(像线段树一样打标记即可)

但是平衡树有一个缺陷,不能在区间层面上进行上述操作

所以我们可以在外面套上一层线段树(线段树套splay" role="presentation" style="position: relative;">splaysplaysplay)
线段树的每一个结点都是一棵 splay s p l a y splay
这样我们在构建的时候,只能单点插入,而且一条路上的每一个结点都要insert,同理,修改也只支持单点

线段树套 splay s p l a y splay,基本上只具有线段树询问区间的功能
并且支持 splay s p l a y splay的几乎所有功能

  • 插入一个数

  • 删除一个数

  • 查询数 x x x的排名(小于x" role="presentation" style="position: relative;">xxx的元素个数)

  • 查询排名为 x x x的数(二分判定)

  • 查询x" role="presentation" style="position: relative;">xxx的前驱(小于 x x x且最大的数)
    (分成logn" role="presentation" style="position: relative;">lognlognlogn个区间,每个区间求前驱,最后求 max m a x max)

  • 查询 x x x的后继(大于x" role="presentation" style="position: relative;">xxx且最小的数)
    (分成 logn l o g n logn个区间,每个区间求后继,最后求 min m i n min)

  • 区间翻转(没写过)

  • 区间加乘(像线段树一样打标记即可)

所以说如果发现一道题需要支持上述操作,我们就可以考虑 splay s p l a y splay
(不过如果只有查询元素排名以及排名为k的元素,还是先考虑一下主席树,毕竟还是代码复杂度越低越好)

经典例题:splay区间翻转
经典例题:线段树+splay(2B)
经典例题:线段树+splay(逆序对个数)
经典例题:splay(括号)
经典例题:splay(项链工厂)

splay s p l a y splay最基础的操作
注意 splay s p l a y splay函数和 del d e l del函数的写法
insert i n s e r t insert的时候不要忘了 update(last) u p d a t e ( l a s t ) update(last)

如果要加上 rever r e v e r rever
我们先调用 find_pm f i n d _ p m find\_pm夹逼我们需要翻转的区间
之后再这个区间打上翻转标记即可
不过,这样我们就需要在 splay s p l a y splay之前 down d o w n down一下
每次 find f i n d find的时候也需要 push p u s h push(多 push p u s h push没有什么坏处)

#include<bits/stdc++.h>using namespace std;const int INF=1e9;
const int N=100010;
int root=0,top=0,pre[N],ch[N][2],size[N],v[N],cnt[N];
bool rev[N]; void clear(int bh) {pre[bh]=ch[bh][0]=ch[bh][1]=size[bh]=v[bh]=cnt[bh]=0;//rev[bh]=0;
}int get(int bh) {return ch[pre[bh]][0]==bh? 0:1;
}void push(int bh) {if (!bh||!rev[bh]) return;if (ch[bh][0]) rev[ch[bh][0]]^=1;if (ch[bh][1]) rev[ch[bh][1]]^=1;swap(ch[bh][0],ch[bh][1]);
}void down(int bh) {if (pre[bh]) down(pre[bh]);push(bh);
}void update(int bh) {if (!bh) return;size[bh]=cnt[bh];if (ch[bh][0]) size[bh]+=size[ch[bh][0]];if (ch[bh][1]) size[bh]+=size[ch[bh][1]];
}void rotate(int bh) {int fa=pre[bh];int grand=pre[fa];int wh=get(bh);ch[fa][wh]=ch[bh][wh^1];pre[ch[fa][wh]]=fa;ch[bh][wh^1]=fa;pre[fa]=bh;pre[bh]=grand;if (grand) ch[grand][ch[grand][0]==fa? 0:1]=bh;update(fa);update(bh);
}void splay(int bh,int mb) {//down(bh);for (int fa;(fa=pre[bh])!=mb;rotate(bh))      if (pre[fa]!=mb)                            //pre[fa]!=mbrotate(get(bh)==get(fa)? fa:bh);if (!mb) root=bh;
}void insert(int x) {int now=root;int last=0;if (!root) {top++;pre[top]=0; ch[top][0]=ch[top][1]=0; size[top]=1; cnt[top]=1; v[top]=x;root=top;                                    //root=top;return;}while (1) {//push(now);if (v[now]==x) {cnt[now]++;update(now);update(last);                            //update(last)splay(now,0);return;}last=now;now=ch[now][v[now]<x];if (!now) {top++;pre[top]=last; ch[top][0]=ch[top][1]=0; size[top]=1; cnt[top]=1; v[top]=x;ch[last][v[last]<x]=top;update(last);splay(top,0);return;}}
}int find_pm(int x) {int now=root,ans=0;while (now) {//push(now);if (v[now]>x) now=ch[now][0];else {ans+=(ch[now][0]? size[ch[now][0]]:0);    //先维护ansif (v[now]==x) {splay(now,0);return ans+1;}ans+=cnt[now];now=ch[now][1];}}return 0;
}int find_x(int k) {int now=root;while (now) {//push(now);if (ch[now][0]&&size[ch[now][0]]>=k) now=ch[now][0];else {int tmp=(ch[now][0])? size[ch[now][0]]:0;tmp+=cnt[now];if (tmp>=k) return now;k-=tmp;now=ch[now][1];}}return 0;
} int qian() {int x=ch[root][0];while (ch[x][1]) x=ch[x][1];return x;
}void del(int x) {find_pm(x);if (cnt[root]>1) {cnt[root]--;update(root);return;}if (!ch[root][0]&&!ch[root][1]) {clear(root);root=0;return;}if (!ch[root][0]) {int k=root;root=ch[k][1];pre[root]=0;clear(k);return;}if (!ch[root][1]) {int k=root;root=ch[k][0];pre[root]=0;clear(k);return;}int k=root;int p=qian();splay(p,0);ch[root][1]=ch[k][1];pre[ch[root][1]]=root;update(root);clear(k);
}int fro(int x) {int ans=0;int now=root;while (now) {//push(now);if (v[now]>=x) now=ch[now][0];else {ans=max(ans,v[now]);now=ch[now][1];}}return ans;
}int nxt(int x) {int ans=INF;int now=root;while (now) {//push(now);if (v[now]<=x) now=ch[now][1];else {ans=min(ans,v[now]);now=ch[now][0];}}return ans;
}int main()
{int x,opt;int n;scanf("%d",&n);while (n--) {scanf("%d%d",&opt,&x);if (opt==1) insert(x);else if (opt==2) del(x);else if (opt==3) printf("%d\n",find_pm(x));else if (opt==4) printf("%d\n",v[find_x(x)]);else if (opt==5) printf("%d\n",fro(x));      //不保证数据中存在x else printf("%d\n",nxt(x));      //不保证数据中存在x } return 0;
}

insert i n s e r t insert函数支持单点插入
如果我们有初始序列,那么推荐如此建树:

int build(int l,int r,int fa)
{if (l>r) return 0;int mid=(l+r)>>1;int now=++top;ch[now][0]=build(l,mid-1,now);ch[now][1]=build(mid+1,r,now);pre[now]=fa;rev[now]=0;v[now]=a[mid]; update(now);return now;
}

LCT

LCT简单讲解
LCT维护子树信息(难)

实际上 LCT L C T LCT就是动态的多个 splay s p l a y splay,重点还是理解操作
(注意,LCT一定是一棵TREE)

LCT L C T LCT中最重要的两个操作(除了 splay s p l a y splay和 rotate r o t a t e rotate)就是 expose e x p o s e expose和 makeroot m a k e r o o t makeroot

expose e x p o s e expose

访问一个结点,该结点到根的路径就会变成偏爱路径,在一棵 splay s p l a y splay上

void expose(int x) {int t=0;while (x) {splay(x);ch[x][1]=t;update(x);           //产生了新儿子,需要update t=x;x=pre[x];}
}

makeroot m a k e r o o t makeroot

换根,把树形结构的根换为 x x x

void makeroot(int x) {expose(x);splay(x);rev[x]^=1;
}

其余的所有操作都是建立在这两个操作上的

link(x,y)" role="presentation" style="position: relative;">link(x,y)link(x,y)link(x,y)

我们要将 x x x和y" role="presentation" style="position: relative;">yyy连接起来
简单的,我们把 x x x连到y" role="presentation" style="position: relative;">yyy上
明确 x,y x , y x,y一定在不同的 splay s p l a y splay内(废话,ta们都不连通)
那么我们首先需要 x x x没有father" role="presentation" style="position: relative;">fatherfatherfather,这样我们才能放心大胆的把ta连向 y y y
什么样的结点没有father" role="presentation" style="position: relative;">fatherfatherfather?根结点!
所以我们 makeroot(x) m a k e r o o t ( x ) makeroot(x)
因为我们没有访问过 x<−>y x < − > y xy这条路径,所以 x x x不会是y" role="presentation" style="position: relative;">yyy的偏爱儿子
换句话说, y y y是不会认x" role="presentation" style="position: relative;">xxx这个儿子的
所以我们只要 pre[x]=y p r e [ x ] = y pre[x]=y就可以了

void link(int x,int y) {makeroot(x);pre[x]=y;
}

cut(x,y) c u t ( x , y ) cut(x,y)

和link一样,我们需要把 x x x变成树的根:makeroot(x)
之后expose(y),splay(y)" role="presentation" style="position: relative;">expose(y),splay(y)expose(y),splay(y)expose(y),splay(y)
我们就得到了一棵以 y y y为根,x&lt;−&gt;y" role="presentation" style="position: relative;">x<−>yx<−>yxy路径的 splay s p l a y splay
x x x是根节点,ta的深度最小,y" role="presentation" style="position: relative;">yyy是辅助树的根节点
所以 x x x一定是y" role="presentation" style="position: relative;">yyy的左儿子
pre[x]=0,ch[y][0]=0 p r e [ x ] = 0 , c h [ y ] [ 0 ] = 0 pre[x]=0,ch[y][0]=0

void cut(int x,int y) {makeroot(x);expose(y);splay(y);ch[y][0]=pre[x]=0;//update(y)
}

find f i n d find

这个操作可以找到 x x x结点在原树中的根结点
(根结点的深度最小,所以在splay" role="presentation" style="position: relative;">splaysplaysplay中最靠左)

int find(int x) {expose(x);splay(x);while (ch[x][0]) x=ch[x][0];return x;
}

两点连通性

bool linked(int x,int y) {return find(x)==find(y);
}

路径权值和

LCT的优点就是ta的形态不固定
我们可以把路径中的一个端点视为根节点(比如说x): makeroot(x) m a k e r o o t ( x ) makeroot(x)
expose(y) e x p o s e ( y ) expose(y), x x x到y" role="presentation" style="position: relative;">yyy的路径就变成了偏爱路径,
splay(y) s p l a y ( y ) splay(y), y y y节点上的sum" role="presentation" style="position: relative;">sumsumsum即为路径权值和

int ask_sum(int x,int y) {makeroot(x);expose(y); splay(y);return sum[y];
}

节点到根的距离:

同理,返回 size s i z e size即可

int ask_dis(int x,int y) {makeroot(x);expose(y); splay(y);return size[y];
}

更改节点值

我们要改变一个结点的权值, 我们当然希望涉及到的结点尽量少
什么样的结点改变ta的值影响的结点维护值最少?根节点!
所以我们首先 makeroot(x) m a k e r o o t ( x ) makeroot(x)
直接修改 x x x的值就可以了
因为我们改变了这个结点的状态,所以需要update(x)" role="presentation" style="position: relative;">update(x)update(x)update(x)

void change_point(int x,int z) {makeroot(x);v[x]=z;update(x);
}

更改路径值

这个操作比较厉害,可以处理路径加乘的问题
我们像线段树的加乘那样每个结点维护加标记和乘标记
每次在push的时候,维护值以及标记
mul[son]=mul[son]∗mul[fa],ad[son]=ad[son]∗mul[fa]+ad[fa] m u l [ s o n ] = m u l [ s o n ] ∗ m u l [ f a ] , a d [ s o n ] = a d [ s o n ] ∗ m u l [ f a ] + a d [ f a ] mul[son]=mul[son]*mul[fa],ad[son]=ad[son]*mul[fa]+ad[fa]

void cal(int x,int a,int b) {     //*a   +bif (!x) return;v[x]=v[x]*a+b;                //不要忘了维护单点值 sum[x]=sum[x]*a+size[x]*b;ad[x]=ad[x]*a+b;mul[x]=mul[x]*a;
}void push(int bh) {if (!bh) return;if (rev[bh]) {...}if (ad[bh]!=0||mul[bh]!=1) {if (ch[bh][0]) cal(ch[bh][0],mul[bh],ad[bh]);if (ch[bh][1]) cal(ch[bh][1],mul[bh],ad[bh]);}mul[bh]=1; ad[bh]=0;
}void add(int x,int y,int z) {makeroot(x);expose(y); splay(y);cal(y,1,z);
}void multi(int x,int y,int z) {makeroot(x);expose(y); splay(y);cal(y,z,0);
}

终上所述,需要询问树上路径权值,同时需要支持加边删边操作的题目,可以考虑LCT

经典例题:LCT基本操作
经典例题:LCT维护路径加乘
经典例题:LCT+并查集(一)
经典例题:LCT+并查集(二)
经典例题:LCT+SAM

LCT中也要判断:if (!bh) return;

const int N=100010;
int pre[N],ch[N][0],size[N],sum[N],v[N],ad[N],mul[N];
bool rev[N];
int q[N];int isroot(int bh) {return ch[pre[bh]][0]!=bh&&ch[pre[bh]][1]!=bh;
}int get(int bh) {return ch[pre[bh]][0]==bh? 0:1;
}void update(int bh) {if (!bh) return;size[bh]=1;if (ch[bh][0]) size[bh]+=size[ch[bh][0]];if (ch[bh][1]) size[bh]+=size[ch[bh][1]];sum[bh]=v[bh];if (ch[bh][0]) sum[bh]+=sum[ch[bh][0]];if (ch[bh][1]) sum[bh]+=sum[ch[bh][1]];
}void rotate(int bh) {int fa=pre[bh];int grand=pre[fa];int wh=get(bh);if (!isroot(fa)) ch[grand][ch[grand][0]==fa? 0:1]=bh;pre[bh]=grand;ch[fa][wh]=ch[bh][wh^1];pre[ch[fa][wh]]=fa;ch[bh][wh^1]=fa;pre[fa]=bh;update(fa);update(bh);
}void push(int bh) {if (!rev[bh]||!bh) return;if (ch[bh][0]) rev[ch[bh][0]]^=1;if (ch[bh][1]) rev[ch[bh][1]]^=1;swap(ch[bh][0],ch[bh][1]);rev[bh]=0;
}void splay(int bh) {int top=0;q[++top]=bh;for (int i=bh;!isroot(i);i=pre[i]) q[++top]=pre[i];while (top) push(q[top--]);for (int fa;!(isroot(bh));rotate(bh))if (!isroot(fa=pre[bh]))rotate(get(fa)==get(bh)? fa:bh);
}void expose(int x) {int t=0;while (x) {splay(x);ch[x][1]=t;update(x);           //产生了新儿子,需要update t=x;x=pre[x];}
}void makeroot(int x) {expose(x);splay(x);rev[x]^=1;
}void link(int x,int y) {makeroot(x);pre[x]=y;
}void cut(int x,int y) {makeroot(x);expose(y);splay(y);ch[y][0]=pre[x]=0;//update(y)
}int find(int x) {expose(x);splay(x);while (ch[x][0]) x=ch[x][0];return x;
}bool linked(int x,int y) {return find(x)==find(y);
}int ask_sum(int x,int y) {makeroot(x);expose(y); splay(y);return sum[y];
}int ask_dis(int x,int y) {makeroot(x);expose(y); splay(y);return size[y];
}void change_point(int x,int z) {makeroot(x);v[x]=z;update(x);
}void cal(int x,int a,int b) {     //*a   +bif (!x) return;v[x]=v[x]*a+b;                //不要忘了维护单点值 sum[x]=sum[x]*a+size[x]*b;ad[x]=ad[x]*a+b;mul[x]=mul[x]*a;
}//void push(int bh) {
//  if (!bh) return;
//  if (rev[bh]) {
//      ...
//  }
//  if (ad[bh]!=0||mul[bh]!=1) {
//      if (ch[bh][0]) cal(ch[bh][0],mul[bh],ad[bh]);
//      if (ch[bh][1]) cal(ch[bh][1],mul[bh],ad[bh]);
//  }
//  mul[bh]=1; ad[bh]=0;
//}void add(int x,int y,int z) {makeroot(x);expose(y); splay(y);cal(y,1,z);
}void multi(int x,int y,int z) {makeroot(x);expose(y); splay(y);cal(y,z,0);
}

平衡树+LCT全纪录相关推荐

  1. swagger 返回json字符串_Net Core微服务入门全纪录(完结)——Ocelot与Swagger

    前言 上一篇[.Net Core微服务入门全纪录(八)--Docker Compose与容器网络]完成了docker-compose.yml文件的编写,最后使用docker compose的一个up指 ...

  2. .net core ocelot 获取路由的mothed_Net Core微服务入门全纪录(四)Ocelot网关(上)

    上一篇[.Net Core微服务入门全纪录(三)--Consul-服务注册与发现(下)]已经使用Consul完成了服务的注册与发现,实际中光有服务注册与发现往往是不够的,我们需要一个统一的入口来连接客 ...

  3. .Net Core微服务入门全纪录(完结)——Ocelot与Swagger

    点击上方蓝字"小黑在哪里"关注我吧 前言 上一篇[.Net Core微服务入门全纪录(八)--Docker Compose与容器网络]完成了docker-compose.yml文件 ...

  4. 更换内存条、固态硬盘、双系统配置全纪录

    联想IdeaPad 310S更换内存条.固态硬盘.双系统配置全纪录 一.更换内存条 二.更换固态 三.重装windows10系统 1.准备工作 2.注意事项 3.制作启动盘 四.加装ubuntu16. ...

  5. RHEL 5.4 下安装和使用 ntop 全纪录(ntop:Linux下可通过Web访问的网络流量监控工具)...

    RHEL 5.4 下安装和使用 ntop 全纪录 1. ntop介绍 Ntop是一种监控网络流量工具,用ntop显示网络的使用情况比其他一些网络管理软件更加直观.详细.Ntop甚至可以列出每个节点计算 ...

  6. 【AI作画】当梵高遇上东方明珠——梵高画风迁移全纪录

    [AI作画]当梵高遇上东方明珠--梵高画风迁移全纪录 视频: [AI作画]当梵高遇上东方明珠--梵高画风迁移全纪录 梵高出生于1853年3月30日荷兰乡村津德尔特的一个新教牧师家庭,早年的他做过职员和 ...

  7. 究极丐版客制化机械键盘制作全纪录(下:程序下载)

    究极丐版客制化机械键盘制作全纪录(下:程序下载) 前排提示:由于键盘制作时间距离写文章的时间比较久远所以不对分享文件的正确性保证!!!! 在比较久之前写了个上篇,当时比较有时间为了简单的记录自己做键盘 ...

  8. 究极丐版客制化机械键盘制作全纪录(上:硬件制作)

    究极丐版客制化键盘制作全纪录 注:2020.12.26 勘误

  9. FRVT赛程全纪录:格灵深瞳全球排名前五

    作者 | 张德兵,格灵深瞳首席科学家&算法部负责人 来源 | 转载自知乎张德兵 最近两个月,格灵深瞳首席科学家&算法部负责人张德兵与算法团队参加了全球人脸识别算法测试(FRVT.Fac ...

最新文章

  1. python xpath语法-Python爬虫 | 解析库Xpath的使用
  2. ASP.NET页面级别的事务
  3. woocommerce 分类到菜单_Woocommerce商店显示分类
  4. java 新建菜单选项_请完成下列Java程序:创建一个下拉式菜单,菜单项包括3个CheckboxM..._考试资料网...
  5. AdapterViewlt;?gt; arg0, View arg1, int arg2, long arg3參数含义
  6. 朋友借我10万并把房产证交给我,写了借条,并在借条上写明了用此房产做抵押,有效吗?
  7. C++queue队列与stack栈
  8. EthBox以太坊开发套件,一键安装部署以太坊开发环境
  9. matlab机器学习之knn算法实例
  10. 搭建属于自己的影视网站
  11. java唯一订单号_java高并发下唯一订单号生成器【16位数字订单号】
  12. ubuntu更新源出现错误
  13. vue中使用echarts词云
  14. Mac中Xcode如何更改编辑器文本字体大小
  15. office办公软件插件推荐word插件excel插件ppt插件
  16. safari支持Ajax同步请求吗,在同步“Ajax”请求之前强制在Webkit(Safari和Chrome)中重绘UI...
  17. 算法篇-2-分治思想-棋盘覆盖归并排序Strasssen矩阵乘法循环赛安排
  18. IEEE论文格式要求
  19. 计算机爱恩斯坦棋游戏,爱恩斯坦棋计算机博弈系统的研究与实现
  20. 猫猫学习ios 之第三方登录友盟实现

热门文章

  1. 解决兼容苹果手机底部黑线的问题
  2. 微信一定要关闭的6个设置
  3. Excel文件如何找回打开密码
  4. 英特尔推出固态硬盘加速应用程序启动
  5. android旅游app动态的,Android平台深秋出游必备APP推荐
  6. TensorFlow——维度变换与Broadcasting
  7. 完整的数字化招采平台(电子化采购管理系统)合规管理解决方案
  8. 2018全国百度地图poi数据
  9. 为什么越来越多年轻人,想去或者留在成都?
  10. 达芬奇密码 第四十章