Splay大全 超详细解释 + 模板

前言

上个月开始打Splay然后被老师看见拉去做USACO题库......

然后这个月继续 对着标的思路打 打完放上去......92分??

对着狂改多次 然后放上去......92分?

干脆直接把标放上去......92分?

真的是气急败♂坏了

然后换了标......重复上续步骤......92分?

抓狂23333~

重复多次步骤 然后跑了57遍 OK finished 过了 心力交瘁 少说码了 3W byte了

然而打的过程早会了的说...... (那你怎么只有92??)

 

好了不扯那么多 现在进入

正题

先翻翻概念了解一下 自行百度 或 戳这里

Splay是什么呢 嗯 就是能让节点比较均匀地分布在树上的二叉搜索树

又慢 代码又长 如果不是区间翻转常用和LCT要用到我才不学

好啦 我在这里再略讲一下操作

先来看看核心的操作

 

1.旋转 (Rotate)

请先打开给的概念的链接 (就是上面的那个戳这里) 翻到那个图的地方 看看两种旋转(配图真的超麻烦啊 这个后面常用到别关了啊)

首先要知道为什么要旋转

我们得到的树呀 不一定是随机数据的说 如果遇到毒瘤出题人 给你几条长为1e5的链 然后让你查询最底下的点

这当然会T的了啦 所以就要通过旋转 把下面一大段的东西掰到上面去 好了那我们说一下旋转

旋转只有一种 那就是上旋 旋转只有两种 那就是左旋和右旋

但是旋转要根据当前点的状态 (是其父亲的哪个儿子) 和 当前点父亲的状态 (有无父亲 是其父亲的哪个儿子) 来判断

举个栗子 如果当前点是他父亲的左儿子 然后你让他左旋 不就断了么 =-= 这是离间他们的亲子关系!(给个愤怒的表情自己体会)

因此要根据其父和其祖父任意两代之间的相对位置判断左右旋 栗子 如果其父是其祖父的左儿子 右旋其父

关于旋转带来的点的元素改变......

首先考虑先改变哪个点的关系 这里先定义当前点 x 及其父亲 y 还有其祖父 z 以及方便左右旋设置的 mode (0为右旋 1为左旋)

祖父的点肯定是不变的啦 但 x 和 y 是会变的 因此 z 的 父子关系会变更 先改这个......哒哒哒 改完啦

然后就是 x 和 y 的 除子节点外的其他改变了的信息了 包括儿子从属 相对父子关系 这里要先判断一下是左旋还是右旋 更新一下 mode Tip:更新儿子从属关系可用异或 (想换成 if 也不是不行) 然后信息更改顺序可以随心换 但别漏了 =-=

最后更新子节点数量 为什么呢? 因为 x 的 儿子 和 y 的 另一个儿子 是 不变的 然后更新完 x 和 y 后 就可以用两点的新的儿子轻易地改变其子节点数量 这里记得先更新 y 的 因为 y 更新后 变成了 x 的 儿子 但是 其子节点数量还没变 最后更新 x 的 子节点数量

下放代码片段

void rotate(int x)
{
	int y = e[x].fa, z = e[y].fa,mode = 0;
	if (e[z].son[0] == y) e[z].son[0] = x;
	else e[z].son[1] = x;
	e[x].fa = z;
	//更新 z 的 有关元素
	if (e[y].son[0] == x) ++mode;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	//更新 x 和 y 的 关系 (互换)
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
	//最后更新子节点数量
}

 

2.展开 (Splay)

我的天呐不小心退了界面又要重打 =-=

其实这个子程序就是 让一个点旋转到根 没旋转到继续旋 用的是一个 while (当前点还有父亲时) {Blabblab}

然后记一下当前点 x 的 父亲 和 祖父 分别为 y 和 z

首先 如果当前点没有祖父 就只用旋转一次 就能到根 直接 rotate 他自己 Tip: 判断左旋右旋 在 rotate 子程序里

如果有祖父的话 就有四种情况了 这个时候 就先翻回上面的那个戳这里 然后翻一下那个图吧

吶 看见没 如果相邻两代的从属关系是一样的 (都是左儿子 或 都是右儿子) 就要先旋转当前点父亲 再旋转他自己

如果从属关系不一样 就直接旋转他自己 那个网站里讲的比较复杂 我翻了其他 dalao 的 程序

居然可以用 异或! 吶 判断两从属关系一样时 == 就得 1 然后 1 ^ 1 = 0 或者 0 ^ 0 = 0 的时候 旋转其父

否则得到 1 就旋转他自己 妙极!

当然如果这样的话 mode 的赋值要丢到 rotate 里去了 放外面我居然 TLE 8 个点 还错了一个

最后别忘了树根 (root) 也要更新哈 改成当前点

一次 splay 后 最坏情况 (链) 当前点插的地方的深度 居然可以减少一半!

所以 有事没事 都要 splay 一下 这是重点!

因此 有时 splay 是为了方便处理问题 有时却是为了调整平衡

然后下放代码片段

void splay(int x)
{
	while (e[x].fa)
	{
		int y = e[x].fa, z = e[y].fa;
		if (z) {
			if ((e[z].son[0] == y) ^ (e[y].son[0] == x)) rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	root = x;
}

 

3.删除 (delete)

这个理解比较麻烦 也分四种情况

1.删除的值不存在

    直接退掉啦

2.删除的值大于一个

    直接删掉一个出现次数就好啦

如果删除的值只有一个就麻烦了 Tip: 下面两种最后都要清空原根的各个元素 新根的父亲变为0

3.删除的值只有一个 且 没有左儿子

    吼吼 这个的话直接把当前根的右儿子变成根就好啦

4.删除的值只有一个 且 有左儿子

    这个我们要先切断左儿子和根的关系 (只用先改左儿子的父亲元素 然后其父亲的修改可以和三一起放判断外面改)

    然后呢 在左儿子处开始推 找到原根的左子树里最大的值

    之后 把该左子树里最大的值旋转上来 并把他当成根

    因为他没右儿子 (他是原根的左字数里最大的) 于是就可以把原根的右儿子接到新根上 更新关系

    然后清空原根的各个元素 就行了

下放代码片段

void del(int p)
{
	int pos = find(root, p);
	if (e[pos].v != p) return;
	splay(pos);
	if (e[pos].tie > 1) {
		--e[pos].tie;
		--e[pos].siz;
		return;
	}
	if (!e[pos].son[0]) {
		e[e[pos].son[1]].fa = 0;
		root = e[pos].son[1];
		if (!root) tot = 0;
	} else {
		e[e[pos].son[0]].fa = 0;
		int lax = find(e[pos].son[0],MAX);
		splay(lax);
		e[root].siz += e[e[pos].son[1]].siz;
		e[root].son[1] = e[pos].son[1];
		e[e[pos].son[1]].fa = root;
	}
	e[pos].v = 0;
	e[pos].tie = 0;
	e[pos].siz = 0;
	e[pos].fa = 0;
	e[pos].son[0] = 0;
	e[pos].son[1] = 0;
}

 

好啦 其他的操作就在代码里标一下吧 可以翻回上面的那个戳这里看看 我可能有遗漏吧(雾

下放 认真缩减但没怎么压行 的 代码 然后 例题仍旧是洛谷的模板

#include <cstdio>
#define MAX 0x7fffffff //2147483647
#define MIN 0x80000000 //-2147483648
using namespace std;
const int MAXN = 100010;
struct Node_attribute {
	int fa,son[2],v,tie,siz;
} e[MAXN];
int root,tot;
void rotate(int x) //见上详解 懒得打了
{
	int y = e[x].fa, z = e[y].fa,mode = 0;
	if (e[z].son[0] == y) e[z].son[0] = x;
	else e[z].son[1] = x;
	e[x].fa = z;
	if (e[y].son[0] == x) ++mode;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
}
void splay(int x) //见上详解 懒得打了
{
	while (e[x].fa)
	{
		int y = e[x].fa, z = e[y].fa;
		if (z) {
			if ((e[z].son[0] == y) ^ (e[y].son[0] == x)) rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	root = x;
}
int find(int now,int w) //查找函数是基于二叉搜索树的有序性(节点值按中序遍历从小到大)
{ //然后这个如果查找不到 就会返回与他相差最小的点
	while (e[now].v != w)
		if (w < e[now].v) { //比较值
			if (e[now].son[0]) now = e[now].son[0]; //值小则往左儿子走
			else break; //找不到 退出
		} else {
			if (e[now].son[1]) now = e[now].son[1]; //值大则往右儿子走
			else break; //找不到 退出
		}
	return now; //返回数值
}
void add(int f,int w)
{ //为了方便添加节点弄的子程序 把两种情况合在一起 可以不用 =-=
	e[++tot].fa = f;
	e[tot].tie = 1;
	e[tot].siz = 1;
	e[tot].v = w;
}
void ins(int p)
{
	if (!tot) { //空树
		add(0,p);
		root = 1;
		return;
	}
	int pos = find(root,p); //寻找点的位置 如果不存在 则为匹配加入点的父亲
	if (e[pos].v == p) e[pos].tie++; else { //如果当前值存在 其出现次数 + 1 否则...
		add(pos,p); //新增节点
		if (p < e[pos].v) e[pos].son[0] = tot; //家庭分配儿子承包责任制
		else e[pos].son[1] = tot;
	} //这两句并不会覆盖原有的点哦 因为..原本这地方不会有点呀 或者只有一个 但不是他的位置
	for (int now = pos ; now ; ++e[now].siz,now = e[now].fa); //当前点及其所有祖先的子节点个数要 + 1
	if (e[pos].v == p) splay(pos); //如果点存在 旋转他到根
	else splay(tot); //否则新加的点旋转到根
}
void del(int p)
{
	int pos = find(root, p);
	if (e[pos].v != p) return;
	splay(pos); //旋转到根方便最后两种情况处理
	if (e[pos].tie > 1) {
		--e[pos].tie;
		--e[pos].siz;
		return;
	}
	if (!e[pos].son[0]) {
		e[e[pos].son[1]].fa = 0;
		root = e[pos].son[1];
		if (!root) tot = 0;
	} else {
		e[e[pos].son[0]].fa = 0;
		int lax = find(e[pos].son[0],MAX);
		splay(lax);
		e[root].siz += e[e[pos].son[1]].siz;
		e[root].son[1] = e[pos].son[1];
		e[e[pos].son[1]].fa = root;
	}
	e[pos].v = 0; //清空原来的点的相关数据
	e[pos].tie = 0;
	e[pos].siz = 0;
	e[pos].fa = 0;
	e[pos].son[0] = 0;
	e[pos].son[1] = 0;
}
int rank(int p) //查找当前数排名
{
	int pos = find(root, p);
	splay(pos);
	return e[e[pos].son[0]].siz + 1;
} //根据他的某个孩子节点数推断他的排名 这里从小排所以是左儿子 当然别忘了 + 1
int k_th(int p) //查找排名第p的数
{
	int now = root; //从根找起 bot bottle 因为某东西实在太长 为了缩减 用该变量代替
	for (int bot = e[e[now].son[0]].siz ; p <= bot ||
		 p > bot + e[now].tie ; bot = e[e[now].son[0]].siz)
		if (p > bot + e[now].tie) {
			p = p - bot - e[now].tie;
			now = e[now].son[1];
		} else now = e[now].son[0];
	/*------------------------------------------------------
	首先now是根 如果他左儿子个数大于p 则说明第p名在左儿子里面
	如果左儿子个数加上他本身出现的个数 那就说明第p名在右儿子里面
	但这里要注意 p要相应地减去now点的左儿子个数和now点的出现次数 因为之后判断是根据新的儿子
	但之前的节点及那个节点的左儿子对他还有影响
	最后更新一下now 然后作为容器的bot也要随之更新
	------------------------------------------------------*/
	return e[now].v; //返回排名第p的点的值
}
int pred(int p) //找前驱 最接近他且严格小于他的点
{
	int pos = find(root,p); //先找到p的位置
	if (e[pos].v < p) return e[pos].v; //如果找到的不是p点 判断一下找到的点的大小 符合就输出
	splay(pos); //随时旋转
	return e[find(e[pos].son[0],MAX)].v;
} //从p的位置的左儿子找里面最大的 就是最接近p的 然后输出其值
int succ(int p) //找后继 最接近他且严格大于他的点
{
	int pos = find(root,p); //先找到p的位置
	if (e[pos].v > p) return e[pos].v; //如果找到的不是p点 判断一下找到的点的大小 符合就输出
	splay(pos); //随时旋转
	return e[find(e[pos].son[1],MIN)].v;
} //从p的位置的右儿子找里面最小的 就是最接近p的 然后输出其值
int main()
{
	int m,mode,x;
	scanf("%d",&m);
	while (m--)
	{
		scanf("%d%d",&mode,&x);
		switch (mode)
		{
			case 1:ins(x);break;
			case 2:del(x);break;
			case 3:printf("%d\n",rank(x));break;
			case 4:printf("%d\n",k_th(x));break;
			case 5:printf("%d\n",pred(x));break;
			case 6:printf("%d\n",succ(x));break;
		}
	}
}

 

然后存一下自己略微简化的代码 区间翻转在下面在下面~

 

#include <cstdio>
using namespace std;
const int MAXN = 100010;
const int MAX = 0x7fffffff;
const int MIN = -0x7fffffff;
struct splay {
	int fa,son[2],siz,tie,v;
} e[MAXN];
int root,tot;
inline int r()
{
	char q = getchar(); int x = 0,y = 0;
	while (q < '0' && q != '-' || q > '9') q = getchar();
	if (q == '-') ++ y,q = getchar();
	while ('0' <= q && q <= '9')
		x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar();
	return y ? -x : x;
}
inline void rotate(int x)
{
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
}
inline void splay(int x)
{
	while (e[x].fa)
	{
		int y = e[x].fa,z = e[y].fa;
		if (z) {
			if ((e[y].son[0] == x) ^ e[z].son[0] == y) rotate(x);
			else rotate(y);
		} rotate(x);
	}
	root = x;
}
inline int find(int now,int w)
{
	while (e[now].v != w)
		if (e[now].v > w) {
			if (e[now].son[0]) now = e[now].son[0];
			else break;
		} else {
			if (e[now].son[1]) now = e[now].son[1];
			else break; 
		}
	return now;
}
inline void add(int f,int w)
{
	e[++tot].fa = f;
	e[tot].siz = 1;
	e[tot].tie = 1;
	e[tot].v = w;
}
inline void ins(int p)
{
	if (!tot) {add(0,p),root = 1; return;}
	int pos = find(root,p);
	if (e[pos].v == p) ++e[pos].tie; else
	add(pos,p),e[pos].son[p > e[pos].v] = tot;
	for (int now = pos ; now ; ++e[now].siz,now = e[now].fa);
	splay(e[pos].v == p ? pos : tot);
}
inline void del(int p)
{
	int pos = find(root,p);
	if (e[pos].v != p) return;
	splay(pos);
	if (e[pos].tie > 1) {--e[pos].tie,--e[pos].siz; return;}
	if (!e[pos].son[0])
	{
		e[e[pos].son[1]].fa = 0;
		root = e[pos].son[1];
		if (!root) tot = 0;
	} else {
		e[e[pos].son[0]].fa = 0;
		int lax = find(e[pos].son[0],MAX);
		splay(lax);
		e[root].siz += e[e[pos].son[1]].siz;
		e[root].son[1] = e[pos].son[1];
		e[e[pos].son[1]].fa = root;
	}
	e[pos].son[0] = 0;
	e[pos].son[1] = 0;
	e[pos].siz = 0;
	e[pos].tie = 0;
	e[pos].fa = 0;
	e[pos].v = 0;
}
inline int rnk(int p)
{
	int pos = find(root,p); splay(pos);
	return e[e[pos].son[0]].siz + 1;
}
inline int kth(int ex)
{
	int now = root;
	for (int bot = e[e[now].son[0]].siz ; ex <= bot ||
		ex > bot + e[now].tie ; bot = e[e[now].son[0]].siz)
		if (ex > bot + e[now].tie) {
			ex -= bot + e[now].tie;
			now = e[now].son[1];
		} else now = e[now].son[0];
	return e[now].v;
}
inline int pre(int p)
{
	int pos = find(root,p); splay(pos);
	return e[pos].v < p ? e[pos].v : e[find(e[pos].son[0],MAX)].v;
}
inline int suc(int p)
{
	int pos = find(root,p); splay(pos);
	return e[pos].v > p ? e[pos].v : e[find(e[pos].son[1],MIN)].v;
}
int main()
{
	int m = r(),mode,x;
	while (m--) {
		mode = r(),x = r();
		switch (mode)
		{
			case 1:ins(x);break;
			case 2:del(x);break;
			case 3:printf("%d\n",rnk(x));break;
			case 4:printf("%d\n",kth(x));break;
			case 5:printf("%d\n",pre(x));break;
			case 6:printf("%d\n",suc(x));break;
		}
	}
	return 0;
}

 

 

2018.10.29 好了好了区间翻转搞定了 我翻成固定的位置了尴尬死了

4.区间翻转(reverse)

话说区间翻转真是个神奇的东西 这里放一个模板题 然后接下来的讨论围绕该题展开

在区间翻转里面呢 我们就要撇开二叉搜索树的性质了 因为翻转后权值显然会错乱 所以我们要维护的是点的位置

然后因为旋转区间的问题.....我们要在区间最左和最右各多加一个点 然后最后处理询问.......等会看代码吧别忘了

这里直接引入线段树中 lazy 的概念 一个点打标记表示其左右子树交换 下面会讲到

我们区间 (此处区间是按相对位置排的) 翻转为什么要交换左右子树?这里直接按套路丢假设了

设我们要查区间 [ l , r ] 把 l - 1 转到 root 处 然后把 r + 1 转到 root 的右儿子处 (因为 r 相对位置大啊)

当然把 r + 1 旋到 root 处 然后把 l - 1 转到 root 的左儿子处 也可以过 因为原理相同 我也试着打了下也就改了几行

我们就不分类讨论了 用第一种举例 看 l ~ r 点所在的位置!此时——

l 和 r 是连在一块的!

然后我们逐层交换 然后不画图了 我解释一下为什么

比如区间 [1,8] 设左边为 [1,3] 右边为 [4,8] 第一次旋转 [4,8] 到左边了 [1,3] 到右边了

然后区间 [4,8] 设左边为 [4,5] 右边为 [6,8] 第二次旋转 [6,8] 到左边了 [4,5] 到右边了

最后区间 [4,5] 设一边一个 翻转后就是 5 和 4

其他区间也这样并上去 就能得到 [8,1] 递减的区间

好了好了我们不必每次区间翻都更新一下 那样超麻烦

打个 lazy 标记 然后就行了

当然这交换只用交换该点儿子就好了啦 当然其他一些东西还是要变变 该点 lazy 要清空 像这样

inline void swap(int x)
{
	int bot = e[x].son[0];
	e[x].son[0] = e[x].son[1];
	e[x].son[1] = bot;
	//(上面)交换儿子位置
	e[e[x].son[0]].laz ^= 1; //这里是有标记变成没的 没标记变成有的
	e[e[x].son[1]].laz ^= 1; //之前讲2.splay中有 ^ 的有关讲解
	e[x].laz = 0; //为什么标记有没有可以换?因为旋转两次等于没旋转啊
}

好了来说说什么时候要更新 lazy 标记

肯定是 splay 的时候啦 但是

因为维护区间时找第 k 个点不是直接找 [k] 的点 而是 siz 第 k 大的点

所以我们从上往下找 kth 的时候就需要更新 lazy 标记 因此在 splay 之前需要下放的 lazy 标记就放好了

这里的 kth 和 上面的 kth 差不多 不过因为按 siz 存 判断是 k 和 左子树 + 1 的大小情况 呐

inline int kth(int now,int ex) //now当前点 ex第k大
{
	if (e[now].laz) swap(now);
	int bot = e[e[now].son[0]].siz + 1;
	if (ex == bot) return now;
	if (ex <  bot) return kth(e[now].son[0],ex);
	return kth(e[now].son[1],ex - bot);
}

而且因为按 siz 存 rotate那块地方的代码也要改改 其中两行变成这样

//	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
//	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + 1;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + 1;
//看得出区别吧= =

好了好了最后放初始建树 初始建树有两种 第一种是 insert 自行上翻

第二种直接 build 为保持平衡 取总的 mid 为 root 然后再建 [1,mid - 1] 和 [mid + 1,n] 记得 mid 不会递归下去!

然后更新各种东西

进去的时候别忘了每个点 size 初始为 1 哈

当前点的父亲 当前层就更新 所以 build 传进去的变量要加一个 f

既然有了 f 那 f 点的儿子顺便就更新了嘛 (主要是如果更新当前点儿子放当前层要算左右两边的 mid)

然后最后最后等儿子们都更新完了 那当前点 size 就加上两子树的 size

FA♂Q 1: 当 l = r 的时候 不用往下 build 了 所以这前面加个判定

FA♂Q 2: 因为 build l , mid - 1 的情况 l 有可能大于 r 这个要在开头就 return 不然.......

FA♂Q 3: 其实这样的话 [0]的 son 会更新到 mid 但我发现并不影响就没管了= =

好了 build 代码放放

inline void build(int l,int r,int f)
{
	if (l > r) return;
	int mid = (l + r) >> 1;
	e[f].son[mid > f] = mid;
	e[mid].fa = f;
	++e[mid].siz;
	if (l == r) return;
	build(l,mid - 1,mid);
	build(mid + 1,r,mid);
	e[mid].siz += e[e[mid].son[0]].siz + e[e[mid].son[1]].siz;
}

好了然后模板题这里再放一个 代码放下面

#include <cstdio>
using namespace std;
const int MAXN = 100010;
struct splay {
	int son[2],fa,siz,laz;
} e[MAXN];
int ans[MAXN],root,tot;
inline int re()
{
	char q = getchar(); int x = 0;
	while (q < '0' || q > '9') q = getchar();
	while ('0' <= q && q <= '9')
		x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar();
	return x;
}
inline void build(int l,int r,int f)
{
	if (l > r) return;
	int mid = (l + r) >> 1;
	e[f].son[mid > f] = mid;
	e[mid].fa = f;
	++e[mid].siz;
	if (l == r) return;
	build(l,mid - 1,mid);
	build(mid + 1,r,mid);
	e[mid].siz += e[e[mid].son[0]].siz + e[e[mid].son[1]].siz;
}
inline void swap(int x)
{
	int bot = e[x].son[0];
	e[x].son[0] = e[x].son[1];
	e[x].son[1] = bot;
	e[e[x].son[0]].laz ^= 1;
	e[e[x].son[1]].laz ^= 1;
	e[x].laz = 0;
}
inline void rotate(int x)
{
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + 1;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + 1;
}
inline void splay(int x,int top)
{
	while (e[x].fa != top)
	{
		int y = e[x].fa,z = e[y].fa;
		if (z != top) {
			if ((e[y].son[0] == x) ^ (e[z].son[0] == y)) rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
}
inline int kth(int now,int ex)
{
	if (e[now].laz) swap(now);
	int bot = e[e[now].son[0]].siz + 1;
	if (ex == bot) return now;
	if (ex <  bot) return kth(e[now].son[0],ex);
	return kth(e[now].son[1],ex - bot);
}
int main()
{
	int n = re(),m = re();
	root = (n + 3) >> 1;
	build(1,n + 2,0);
	while (m--)
	{
		int i = kth(root,re()),j = kth(root,re() + 2);
		splay(i,0),root = i;
		splay(j,root);
		e[e[e[root].son[1]].son[0]].laz ^= 1;
	}
	for (int a = 2 ; a <= n + 1 ; ++ a) printf("%d ",kth(root,a) - 1);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值