首页 > 其他分享 >[题解]P3391 文艺平衡树 - Splay解法

[题解]P3391 文艺平衡树 - Splay解法

时间:2024-06-20 18:43:22浏览次数:22  
标签:splay ch fa int 题解 Splay P3391 节点 define

P3391 【模板】文艺平衡树

给定序列\(1,2,\dots,n\),接下来\(m\)次操作,每次操作给定\(l,r\),你需要翻转\([l,r]\)。

所有操作结束后,请输出这个序列。


我们先从“普通平衡树”这一题出发,思考一下Splay操作的本质。

我们把一个节点Splay到根节点后,中序遍历在自己前面的节点都在左边,中序遍历在自己后面的都在右边。

发现了么?Splay操作和点权是无关的。如果我们将splay(x)——将\(x\)转移到根节点,修改为splay(x,y)——将\(x\)转移到\(y\)节点下方(\(y=0\)则表示转移到根节点)。那么splay(x,y)所做的,就是将\(y\)的子树中,有\(x\)的那棵子树中所有的节点,按与\(x\)中序遍历的先后关系进行调整。

回归这道题。我们先构建一棵中序遍历是\(1,2,\dots,n\)的二叉树(你可以很简单地考虑一条链)。接下来我们思考怎样区间翻转。

我们先考虑一棵树,要想翻转其中序遍历,我们只需要把每个节点的左右子树都翻转一遍即可(不要在意破坏BST的性质,因为这棵树根本就不是BST)。当然这一操作如果实时更新会TLE,所以我们像线段树那样,给根节点打个懒标记,后期需要用到子节点就先下传标记(并交换根节点的左右子树)。

如果我们能把要翻转的\([l,r]\)的所有节点弄到一棵树上去就好了……

现在是Splay大放异彩的时候了。

我们先找到\(l\)在中序遍历中的前驱节点\(L\),\(r\)在中序遍历中的后继节点\(R\)(为了防止找不到,我们设置两个哨兵\(n+1\)和\(0\)一并加入二叉树)。splay(L,0),再splay(R,L)

我们先将\(L\)转移到根节点,再把\(R\)转移到\(L\)的下面(右子树)。

那么显然,此时\(R\)的左子树的中序遍历位置就都在\(l,r\)之间了。

我们给\(R\)打一个标记即可。

就酱。


受上面操作的启发,我们还可以设计出以下操作(一般这些操作都需要哨兵\(n+1\)和\(0\)的辅助,同时splay需要接受\(2\)个参数):

  • \([l,r]\)区间删除:和上边一样,splay(l,0)splay(r,l),断开\(r\)与其左子树的连接。
  • 合并以\(x,y\)为根的\(2\)颗Splay树(前提是两树都不为空,且\(x\)中的最大值\(<y\)中的最小值):把\(x\)中的最大值Splay到根节点,将其右子节点设为\(y\)并更新自己的信息即可。

实现细节:

  • 别忘了find(k)(找中序遍历第\(k\)个)和ltr(u)(中序遍历输出结果)要调用pushdown
此题的代码
#include<bits/stdc++.h>
#define int long long
#define N 100010
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
#define ch(x,y) tr[x].ch[y]
#define fa(x) tr[x].fa
#define v(x) tr[x].v
#define siz(x) tr[x].siz
#define tag(x) tr[x].tag
#define MAX LLONG_MAX
#define MIN LLONG_MIN
using namespace std;
struct tree{
	int ch[2],siz,fa,v;
	bool tag;
}tr[N];
int n,m,cnt,root;
int newnode(int v){v(++cnt)=v,siz(cnt)=1;return cnt;}
void update(int u){siz(u)=siz(lc(u))+siz(rc(u))+1;}
bool get(int u){return u==rc(fa(u));}
void rot(int x){//通过旋转把x提升1层
	int y=fa(x),z=fa(y);//保证y!=0
	bool dir=get(x),tdir=get(y);
	if(ch(x,!dir)) fa(ch(x,!dir))=y;
	ch(y,dir)=ch(x,!dir);
	ch(x,!dir)=y;
	fa(y)=x,fa(x)=z;
	if(z) ch(z,tdir)=x;
	update(y),update(x);
}
void splay(int x,int y){//核心操作
	for(int f;(f=fa(x))!=y;rot(x))
		if(fa(f)!=y) rot(get(f)==get(x)?f:x);
	if(y==0) root=x;
}
void pushdown(int x){//下放标记
	if(tag(x)){
		swap(lc(x),rc(x));
		tag(lc(x))^=1;
		tag(rc(x))^=1;
		tag(x)=0;
	}
}
void ins(int v){//插入
	int u=root,f=0;
	while(u) f=u,u=ch(u,v>v(u));//>在右,<=在左
	u=newnode(v),fa(u)=f;
	ch(f,v>v(f))=u;
	splay(u,0);
}
int find(int num){//中序遍历第num个是多少
	int u=root;
	while(u){
		pushdown(u);
		if(num==siz(lc(u))+1) return u;
		if(num<=siz(lc(u))) u=lc(u);
		else num-=siz(lc(u))+1,u=rc(u);
	}
	return -1;
}
void ltr(int u){//中序遍历,输出
	if(!u) return;
	pushdown(u);
	ltr(lc(u));
	if(v(u)!=MIN&&v(u)!=MAX) cout<<v(u)<<" ";
	ltr(rc(u));
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>m;
	ins(MIN),ins(MAX);
	for(int i=1;i<=n;i++) ins(i);
	while(m--){
		int l,r;
		cin>>l>>r;
		l=find(l),r=find(r+2);
        //因为有极小值,所以查询的名次都要+1
		splay(l,0);
		splay(r,l);
		if(lc(r)) tag(lc(r))^=1;
	}
	ltr(root);
	return 0;
}

标签:splay,ch,fa,int,题解,Splay,P3391,节点,define
From: https://www.cnblogs.com/Sinktank/p/18258971

相关文章

  • CF484E 题解
    很好的数据结构题,加深了蒟蒻对于可持久化线段树的理解。题意给定一个序列\(\{a_n\}\)(\(1\len\le10^5,1\lea_i\le10^9\)),有\(m\)($\lem\le10^5$)个询问,每次询问给出\(l,r,k\),表示询问区间\([l,r]\)里长度为\(k\)的子区间的最小值最大是多少。题......
  • CF166D 题解
    看到其它题解代码颇长,蒟蒻在此贡献一个二分图最大匹配的解法。题意鞋店里有\(n\)(\(1\len\le10^5\))双鞋子,第\(i\)双鞋子有价格\(c_i\)与尺码\(s_i\)(\(1\lec_i,s_i\le10^9\)),保证\(s_i\)互不相同。有\(m\)(\(1\lem\le10^5\))位顾客光临,第\(......
  • SOLIDWORKS常见使用问题解决方案 慧德敏学
    本文Solidkits为大家分享SOLIDWORKS常见使用问题解决方案,让我们一起学习,使设计工作效率更高。1、如何隐藏装配体里头的零件?—右键点击零件或者树状图中的零件名字,然后点眼睛那个图标。更快捷的方式,只需要把鼠标放到那个零件上,按一下Tab,隐藏了。2、如何恢复隐藏装配体里头的零......
  • CF988D Points and Powers of Two 题解
    题目传送门题目大意题目描述在坐标线上有nnn个不同的点,第iii......
  • CF212D 题解
    根据此题首次学到二阶差分Trick,好题。题意给出一个序列\(\{a_n\}\),满足\(n\le10^6,1\lea_i\le10^9\),定义函数\(f(i,k)\)为:\[f(i,k)=\min\limits_{j=i}^{i+k-1}a_j\]你需要回答\(m\)个询问,每次询问给出一个整数\(k\)(\(1\lek\len\)),求所有\(f(i,k......
  • [笔记]Splay树
    前置知识:树的左旋、右旋。Splay树是一种平衡树。能够做到每个操作均摊\(O(\logN)\)。前言与上文AVL树不同之处在于,AVL树在任何操作结束后,都能保证每个节点的左右子树高度相差不超过\(1\)。相应地,每个操作都是严格的\(O(\logN)\)。而Splay树并没有对“平衡”的确切定义,任何结......
  • DataTrigger 数据触发器触发动画的方式及问题解决
    在WPF中通过触发器实现动画的方式很常见,这里记录一下再使用DataTrigger数据触发器触发动画的一些经验,以便备忘。一、数据触发器DataTrigger与普通的触发器Trigger区别:Trigger普通触发器<!--样式--><StyleTargetType="TextBlock"><Style.Triggers><!--这里......
  • 2024年BCSP-X初赛真题解析(小高组)
    学习C++从娃娃抓起!学习下帝都的对标CSP的BCSP考试,记录下CSP-J备考学习的每一个瞬间。单选题第1题计算机在工作过程中突然停电,()中的信息不会丢失。A.显存B.寄存器C.RAMD.ROM【答案】:D第2题中缀表达式a*(b+c)-d的后缀形式是()。A.abcd*±B.abc+*d-C.abc*+d-D.-+......
  • P6261 [ICPC2019 WF] Traffic Blights 题解
    思路考虑题目要求的是什么。假设\(p_i\)代表通过前\(i\)个红绿灯的概率。那么我们的答案即为\(p_i-p_{i-1}\)。不妨设\(w_i=r_i+g_i\)。我们的限制条件类似:\[t\not\equiva_i\pmodw_i\]那么所有红绿灯会形成周期\(lcm(w_1,w_2,\cdots,w_n)\)。由于\(2019!\)肯......
  • P10540 [THUPC2024] 古明地枣的袜子 题解
    题意:一个长为\(n\)的序列\(a\),初始全为零。\(n\)个操作,第\(i\)个操作形如给\(a_1,\cdots,a_{x_i}\)加上\(y_i\)。\(m\)次查询,给定\(l,r\),求对\(a\)执行第\(l\simr\)个操作后数列\(a\)的全局最大值。\(1\len,m\le5\cdot10^5,1\lex_i,|y_i|\len,1\lel\ler\len\),时间限......