• 【洛谷P3690】【模板】Link Cut Tree (动态树)


    题目

    题目链接:https://www.luogu.com.cn/problem/P3690
    给定 (n) 个点以及每个点的权值,要你处理接下来的 (m) 个操作。
    操作有四种,操作从 (0)(3) 编号。点从 (1)(n) 编号。

    • 0 x y 代表询问从 (x)(y) 的路径上的点的权值的 ( ext{xor}) 和。保证 (x)(y) 是联通的。
    • 1 x y 代表连接 (x)(y),若 (x)(y) 已经联通则无需连接。
    • 2 x y 代表删除边 ((x,y)),不保证边 ((x,y)) 存在。
    • 3 x y 代表将点 (x) 上的权值变成 (y)

    思路

    LCT 维护了若干棵 Splay,每一棵 Splay 维护的是树上深度连续递增的一条链,且中序遍历为这条链深度从浅到深的节点。
    与重剖和长剖不同,LCT 树剖采用的是实链剖分,也就是每一个点与其儿子中,最多有一条是实边,这条实边所连接的儿子与该点位于同一棵 Splay 中。其他儿子都不与该点位于同一棵 Splay。这样就保证了每一棵 Splay 对应原树中的一条链。
    虽然虚边不需要在同一个 Splay 中,但是虚边所对应的儿子的 (fa) 数组依然指向其父亲,也就是认父不认子。
    LCT 最关键的两个操作是 access 和 split,分别是打通一个点到树根的链,扔进一棵 Splay 里;和打通两个点所在链。
    对于 access 操作,我们直接不断将 (x) splay 到其所在 Splay 的根,然后判断如果依然有父节点,说明上面是一条虚边而不是到达了树根,那么就打通这条虚边,将原来的实边变为虚边,然后继续操作直到树根。
    对于 split 操作,我们先把 (x) access 到树根,然后将 (y) splay 到其 Splay 的根,如果 (y)(x) 并不在同一棵 Splay 中,那么就令 (fa_y=x)
    Link-Cut Tree 自然支持 Link 和 Cut,也就是连边和删边,利用各种辅助操作即可完成。
    当然 LCT 还有很多辅助操作如 findrt,makert,notrt 等,以及 Splay 的基本操作 rotate,splay。在此不一一细说。可以看 Blog
    时间复杂度 (O(mlog n))

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=100010;
    int n,m;
    
    struct LCT
    {
    	int val[N],ch[N][2],fa[N],xors[N];
    	bool rev[N];
    	
    	void pushup(int x)
    	{
    		xors[x]=xors[ch[x][0]]^xors[ch[x][1]]^val[x];
    	}
    	
    	void pushdown(int x)
    	{
    		if (rev[x])
    		{
    			int c=ch[x][0]; swap(ch[c][0],ch[c][1]);
    			c=ch[x][1]; swap(ch[c][0],ch[c][1]);
    			rev[ch[x][0]]^=1; rev[ch[x][1]]^=1; rev[x]=0;
    		}
    	}
    	
    	bool notrt(int x)
    	{
    		return (ch[fa[x]][0]==x) || (ch[fa[x]][1]==x);
    	}
    	
    	int pos(int x)
    	{
    		return ch[fa[x]][1]==x;
    	}
    	
    	void rotate(int x)
    	{
    		int y=fa[x],z=fa[y],k=pos(x),c=ch[x][k^1];
    		if (notrt(y)) ch[z][pos(y)]=x; ch[x][k^1]=y; ch[y][k]=c;
    		if (c) fa[c]=y; fa[y]=x; fa[x]=z;
    		pushup(y); pushup(x);
    	}
    	
    	void splay(int x)
    	{
    		stack<int> st;
    		st.push(x);
    		for (int y=x;notrt(y);y=fa[y]) st.push(fa[y]);
    		for (;st.size();st.pop()) pushdown(st.top());
    		while (notrt(x))
    		{
    			if (notrt(fa[x]))
    				rotate(pos(x)==pos(fa[x])?fa[x]:x);
    			rotate(x);
    		}
    		pushup(x);
    	}
    	
    	void access(int x)
    	{
    		for (int y=0;x;y=x,x=fa[x])
    		{
    			splay(x); ch[x][1]=y;
    			pushup(x);
    		}
    	}
    	
    	int findrt(int x)
    	{
    		access(x); splay(x);
    		for (;ch[x][0];x=ch[x][0])
    			pushdown(x);
    		splay(x);
    		return x;
    	}
    	
    	void makert(int x)
    	{
    		access(x); splay(x);
    		rev[x]^=1; swap(ch[x][0],ch[x][1]);
    	}
    	
    	void split(int x,int y)
    	{
    		makert(x);
    		access(y);
    		splay(y);
    	}
    	
    	void link(int x,int y)
    	{
    		makert(x);
    		if (findrt(y)!=x) fa[x]=y;
    		access(x);
    	}
    	
    	void cut(int x,int y)
    	{
    		makert(x);
    		if (findrt(y)!=x) return;
    		splay(x);
    		if (fa[y]==x && !ch[y][0])
    			fa[y]=ch[x][1]=0;
    		pushup(x);
    	}
    	
    	int query(int x,int y)
    	{
    		split(x,y);
    		return xors[y];
    	}
    }lct;
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++)
    		scanf("%d",&lct.val[i]);
    	while (m--)
    	{
    		int opt,x,y;
    		scanf("%d%d%d",&opt,&x,&y);
    		if (opt==0) printf("%d
    ",lct.query(x,y));
    		if (opt==1) lct.link(x,y);
    		if (opt==2) lct.cut(x,y);
    		if (opt==3) lct.splay(x),lct.val[x]=y;
    	}
    	return 0;
    }
    
  • 相关阅读:
    spring mvc controller间跳转 重定向 传参
    SpringMVC拦截器(资源和权限管理)
    Spring3 MVC 拦截器拦截不到的问题
    使用HandlerInterceptor实现简单的授权
    同一个form里,不管哪个 submit 都是直接提交form表单里的内容
    AJax+springMVC+JQURY.GET--注册界面即时刷新用户名是否存在
    Ajax异步检查用户名是否存在(附Demo下载)
    Ajax注册表单用户名实时验证
    SpringMVC记住密码功能(实例)
    CocoaPods停在Analyzing dependencies解决方案
  • 原文地址:https://www.cnblogs.com/stoorz/p/14242855.html
Copyright © 2020-2023  润新知