1.引出问题

ACwing上的模板题(没必要上Luogu)

动态树问题,要支持加/删边,和查询/修改操作。
如果树边没有改变,可以用树链剖分解决。
怎么转到动态树呢?实链剖分,实儿子没有什么实际含义,灵活可变。
我们可以用Splay维护这些实链。

2.辅助树

1.每一个Splay维护一个实链的信息。

2.原树与辅助树的节点对应。

3.在LCT中每棵Splay的根节点的父亲节点指向原树中这条链的父亲节点。

4.认父不认子,很重要的性质,保证辅助树分为多颗Splay。父亲找不到虚儿子。

5.只用考虑辅助树,原树没用。

3.具体操作

下面抄自OI Wiki(代码质量不好)FlashHu的博客(WC讲课老师推荐)

为保证时间复杂度,要在改变辅助树后Splay。

函数顺序大致参考WC讲课老师金靖的PPT(害怕侵权,不作上传)。

I.Splay原有操作

1.splay

与一般Splay不同,这里要判断x的父亲是否为根。还要更新下传根到点的所有懒标记。(见update操作)

void splay(int x)
{
	update(x);
	for(int y;y=fa[x],!isroot(x);rotate(x))
	{
		if(!isroot(y))
		{
			rotate(get(x)==get(y)?y:x);
		}
	}
	pushup(x);
}

2.rotate

也要判父亲是否为根。

void splay(int x)
{
	update(x);
	for(int y;y=fa[x],!isroot(x);rotate(x))
	{
		if(!isroot(y))
		{
			rotate(get(x)==get(y)?y:x);
		}
	}
	pushup(x);
}

II.LCT特有操作

1.Access(x)

x到原树根变成实链,某些边受影响可能变虚边。

(所有操作的基础,LCT的关键)

 

统共4步:

将当前节点转到根上,Splay基操;之前节点变成右儿子(路径放到实链上);更新;转到父亲,继续第1步。

这样,和父亲的边就在实链上了。同时,由于一直放在右儿子,x最终会变成Splay中最右的点。

void access(int x)
{
	int y;
	for(y=0;x;x=fa[y=x])
	{
		splay(x),ch[x][1]=y,pushup(x);
	}
}

2.makeroot

将x变为原树的根

通过access让x和原树的根在同一颗Splay中,子孙都要左右翻转(打懒标记)。

HQX解释:x在access后成为Splay最右的点(中序遍历最后),翻转整个Splay(每层都要翻转),x成为成了最左的点(深度最小)。

由于放到根上比较特殊,也很好做,所以这个函数可以大大地帮助我们实现其他操作。

void makeroot(int x)
{
	access(x);
	splay(x);
	rev(x);
}

3.findroot

找x所在原树的根。

把x与树根放在同一条链,维护Splay,找最左的点(根),记得下传标记。(同上HQX所述)

int findroot(int x)
{
	access(x);
	splay(x);
	while(ch[x][0]){
		pushdown(x);
		x=ch[x][0];
	}
	splay(x);
	return x;
}

4.split

将x到y的路径整出来

把x变为原树的根,让y与原树根(x)在同一条链,y变为Splay的根,所在Splay包含x到y路径

void split(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y); 
}

5.link

把x和y连边

把x变为原树的根,利用根无父亲的特点,让y成为x的父亲,完成连边。

记得在makeroot后,判断x和y是否在同一颗原树内,防止非法连边破坏树的结构。

void link(int x,int y)
{
	makeroot(x);
	int z=findroot(y);
	if(z!=x)fa[x]=y;
}

6.cut

把x和y连的边删掉

把x变为原树的根,y只能为x的右儿子(根深度最浅,没有左儿子即更浅的点),直接断开。

记得在makeroot后,判断x和y是否在同一颗原树内,防止断不存在的边。

void cut(int x,int y)
{
	makeroot(x);
	int z=findroot(y);
	if(z==x&&fa[y]==x&&!ch[y][0])
	{
		fa[y]=ch[x][1]=0;
		pushup(x);
	}
} 

7.reverse

void rev(int x)
{
	swap(ch[x][0],ch[x][1]);
	tag[x]^=1; 
}

翻转操作

左右儿子翻转,给当前点更改懒标记。

8.update

更新下传根到点的所有懒标记

顺序绝对不能搞反,通过递归实现父亲先pushdown。

void update(int x)
{
	if(!isroot(x))update(fa[x]);
	pushdown(x);
}

III.具体操作

1.删/加边:cut/link(x,y)

2.x到y路径异或和:split(x,y),答案在y上。

3.修改x的权值:splay(x),x变为Splay根节点,再修改x,避免对其他点造成影响。

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define inf 1000000007
using namespace std;
int n,m,a[1000005];
long long read()
{
	long long x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return x*f;
}
struct LCT
{
	
	int next[1000005],to[1000005],h[1000005],cnt;
	int fa[1000005],ch[1000005][2],sz[1000005],tag[1000005];
	int s[1000005],v[1000005];
	void pushup(int x)
	{
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]];
		s[x]=s[ch[x][0]]^s[ch[x][1]]^v[x];
	}
	void rev(int x)
	{
		swap(ch[x][0],ch[x][1]);
		tag[x]^=1; 
	}
	void pushdown(int x)
	{
		if(!tag[x])return;
		if(ch[x][0])rev(ch[x][0]);
		if(ch[x][1])rev(ch[x][1]);
		tag[x]=0;
	}
	bool isroot(int x)
	{
		return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
	}
	int get(int x){return x==ch[fa[x]][1];}
	void rotate(int x)
	{
		int y=fa[x],z=fa[y],k=get(x),kk=get(y);
		if(!isroot(y))ch[z][kk]=x;
		ch[y][k]=ch[x][k^1];
		fa[ch[x][k^1]]=y;
		ch[x][k^1]=y;
		fa[y]=x;fa[x]=z;
		pushup(y);pushup(x);
	}
	void update(int x)
	{
		if(!isroot(x))update(fa[x]);
		pushdown(x);
	}
	void splay(int x)
	{
		update(x);
		for(int y;y=fa[x],!isroot(x);rotate(x))
		{
			if(!isroot(y))
			{
				rotate(get(x)==get(y)?y:x);
			}
		}
		pushup(x);
	}
	void access(int x)
	{
		int y;
		for(y=0;x;x=fa[y=x])
		{
			splay(x),ch[x][1]=y,pushup(x);
		}
		//return y;
	}
	void makeroot(int x)
	{
		access(x);
		splay(x);
		rev(x);
	}
	int findroot(int x)
	{
		access(x);
		splay(x);
		while(ch[x][0]){
			pushdown(x);
			x=ch[x][0];
		}
		splay(x);
		return x;
	}
	void split(int x,int y)
	{
		makeroot(x);
		access(y);
		splay(y); 
	}
	void link(int x,int y)
	{
		makeroot(x);
		int z=findroot(y);
		if(z!=x)fa[x]=y;
	}
	void cut(int x,int y)
	{
		makeroot(x);
		int z=findroot(y);
		if(z==x&&fa[y]==x&&!ch[y][0])
		{
			fa[y]=ch[x][1]=0;
			pushup(x);
		}
	} 
}t;
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		t.v[i]=read();
	}
	for(int i=1,opt,x,y;i<=m;i++)
	{
		opt=read(),x=read(),y=read();
		if(opt==0)
		{
			t.split(x,y);
			printf("%d\n",t.s[y]);
		}
		if(opt==1)
		{
			t.link(x,y);
		}
		if(opt==2)
		{
			t.cut(x,y);
		}
		if(opt==3)
		{
			t.splay(x);
			t.v[x]=y;
			t.pushup(x);
		}
	}
	return 0;
}

4.写在最后

以前总觉得LCT是难以企及的高峰,是洪水猛兽。

但现在仔细钻研,回头再看,并没有那么难。

模板题只有2k+bytes,思路也很明了。

(但是非模板题都很难)

 

posted @ 2022-01-26 21:28 HYDcn666_JZOJ 阅读(34) 评论(0) 推荐(0) 编辑
摘要: 问题 解同余方程(有解) xa1(mod p1)xa1(mod p1) xa2(mod p2)xa2(mod p2) xan(mod pn)xan(mod pn) 相较于一般的中国剩余定理,这题没有aimiaimi的限制 所以要用扩展中国剩余定理(excrtexcrt阅读全文
posted @ 2021-07-19 21:41 HYDcn666_JZOJ 阅读(130) 评论(0) 推荐(0) 编辑
摘要: 问题: 证明同余方程组 xa1(mod m1)xa1(mod m1) xa2(mod m2)xa2(mod m2) 有解当且仅当(m1,m2)|(a1a2)\(.:,\)[m1,m2](m1,m2)|(a1a2)\(.:,\)[m1,m2]唯一. 解法: 解法(人话,也许错误很多): 设$x=m1k+a1=m2k1+a2 阅读全文
posted @ 2021-07-19 14:41 HYDcn666_JZOJ 阅读(244) 评论(0) 推荐(0) 编辑
摘要: 组合数C 1.最早的思路 C如果用C(n,m)=n!/(m!(n-m)!),需要预处理阶乘, 这样的话,会TLE+MLE(自行脑补)。 2.暴力出奇迹 既然阶乘不行,那我们回归暴力 可以将阶乘枚举,然后分解质因数 再用高精度乘法一个个处理, 这样,就不需要什么除法了 3.原理呢? 还是这个式子:C( 阅读全文
posted @ 2020-08-14 19:53 HYDcn666_JZOJ 阅读(343) 评论(0) 推荐(0) 编辑
摘要: 【SDOI2009】晨跑 题面 Description Elaxia最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧撑、仰卧起坐等等,不过到目前为止,他坚持下来的只有晨跑。现在给出一张学校附近的地图,这张地图中包含N个十字路口和M条街道,Elaxia只能从一个十字路口跑向另外一个十字路口,街 阅读全文
posted @ 2020-08-13 22:15 HYDcn666_JZOJ 阅读(198) 评论(0) 推荐(0) 编辑
摘要: 第一次修改2020/8/14:1.增加了Kruskal;2.对一些不准确、不详细的地方进行了改动;3.别问我为什么Kruskal用链式前向星,而Prim用邻接矩阵(因为能用Prim你的空间也不会炸) 最小生成树的定义 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个 阅读全文
posted @ 2019-11-16 15:36 HYDcn666_JZOJ 阅读(3161) 评论(3) 推荐(0) 编辑
摘要: 将博客搬至CSDN 阅读全文
posted @ 2022-07-19 11:42 HYDcn666_JZOJ 阅读(17) 评论(0) 推荐(0) 编辑
摘要: 题目传送门 题目大意 一个1~n的排列,进行m次区间升/降序排序操作,问x最终所在的位置。 思路 这道题不复杂,但不好想,还容易想到一些没用的方法(如平衡树)。 我们尝试换个思路,我们要找的这个位置的值满足>=x且不满足>=x+1。 这看起来是废话(与=x完全等价),但是可以拆分上面两个限制条件,将 阅读全文
posted @ 2022-02-10 22:14 HYDcn666_JZOJ 阅读(65) 评论(0) 推荐(0) 编辑
摘要: 线段树套平衡树 【题目】 先引入模板题:二逼平衡树。 本题要求实现一个数据结构,可以查询: 区间内x的排名/前驱/后继,排名为k的数;支持单点修改。 【思考】 来源:二逼平衡树-线段树套无旋Treap 看到排名,可以联想到平衡树,但平衡树并不能直接实现区间操作。 看到区间修改,可以联想到线段树。 我 阅读全文
posted @ 2022-01-23 18:01 HYDcn666_JZOJ 阅读(60) 评论(0) 推荐(0) 编辑
摘要: 1.现状与不足 众所周知,快速幂求逆元是O(log)O(log)的。 在一些无良题目中,求逆次数过多,但数不大,需要预处理。 此时,我们就需要线性求逆了。 2.推导 设模数p=k×i+r 因此k×i+r0(mod  p) 同乘$(i^{-1} \times r^{ 阅读全文
posted @ 2021-11-10 22:01 HYDcn666_JZOJ 阅读(48) 评论(0) 推荐(0) 编辑
摘要: 防遗忘故作此文 1.待修改莫队 普通莫队不支持修改,要支持修改,需在原有基础上增加一维:时间。 时间变更而修改时,直接在数组修改;特殊地,如其位置在区间内需要add,del操作。 2.例题:数颜色 题意:一个序列,支持修改一个位置的颜色+查询区间颜色数,可离线。 思路:与1中内容如出一辙。 code 阅读全文
posted @ 2021-11-10 21:32 HYDcn666_JZOJ 阅读(31) 评论(0) 推荐(0) 编辑
摘要: https://gmoj.net/senior/#main/show/7241 题意 非常绕,看了好久。 给定K个图,要求建一个新图G,G的点以一个k元组(j1~jK)表示,ji表示第i幅图的点ji。G中两个点(j1~jK),(k1~kK)连边,要求满足所有ji与ki连边。 然后求(1,…,1)到每 阅读全文
posted @ 2021-08-17 22:07 HYDcn666_JZOJ 阅读(50) 评论(0) 推荐(0) 编辑
摘要: 题意:对于i上一个同颜色的位置j,ans+=(j+1~i-1的颜色种类) 做法:考场时傻逼了没想到,题做得少。枚举R,统计区间和。由于同种颜色数量不影响贡献R有顺序只用考虑L,每次颜色重复就把原位置-1,当前位置+1。 #include<bits/stdc++.h> #define inf 1000 阅读全文
posted @ 2021-08-14 15:58 HYDcn666_JZOJ 阅读(64) 评论(0) 推荐(0) 编辑
摘要: 很早之前切的,但当时主要是记式子,不会推。 献上式子(lzc大佬推的) 我没打下面那种,也口胡一下。预处理φ及其前缀和。然后数论分块 阅读全文
posted @ 2021-07-20 07:36 HYDcn666_JZOJ 阅读(31) 评论(0) 推荐(0) 编辑
点击右上角即可分享
微信分享提示