• luogu3690 【模板】 Link Cut Tree(动态树)


    题目大意

    给定n个点以及每个点的权值,要你处理接下来的m个操作。操作有4种。操作从0到3编号。点从1到n编号。
    0.询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。
    1.代表连接x到y,若x到y已经联通则无需连接。
    2.后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。
    3.将点x上的权值变成y。

    总体思路

    在动态树中,结点是可以通过Link和Cut进行变换的,对于维护静态树的树链剖分可就不管用了。此时如何效率高地对多个点的值进行存储处理呢?如果可以利用上结点可以灵活地转动的splay树,这样就可以通过存储每个点的子树中的值的方式满足一个点对多个点的值进行存储了呀!

    回顾Splay树,其实质上维护的是一个值递增的数列,每一棵子树维护的都是数列中连续的一段区间。那么在这个原图中,对于每一条链,上面的节点的深度根据父子关系都是递增的。如果我们把这条链上的节点按照深度排成一排,这也是一个数列,所以可以用Splay树来解决。

    关于这棵splay树

    splay树维护的是原图中的一条链,节点的key值为原图中节点的深度,每个节点维护其子树的Xor和。每个子树都维护一段子链。(注意,splay树中的排列方式与其在原图中具体谁与谁相邻无关)因此,splay树根节点的Xor值便是整条链的Xor和,它的父节点是连接splay树所维护的链的某个节点(具体为splay树最靠左的节点)的原图中的父节点。

    既然这叫做动态树,所以树可以是不断变换的。如果我们要对两点间的路径进行查询,只要把一个点放在splay的树根上且没有右孩子(位于所在链链顶),另一个点放在splay树所维护的链的链底,这样就可以知道整条链上的值了。

    基本操作

    Access

    Access(Node *cur)是生成结点cur到顶端Splay树根的一条链,需要用到链的断开与连接。具体操作就是从下往上找。对于一个splay树中的结点cur,将cur通过Splay使其成为当前splay树的树根,然后将当前这个树根cur的右孩子断开(此时cur就成为了链底),将其设为之前找到的Splay树的树根prev(一开始prev是个NULL,所以cur没有右孩子),并更新cur存储的Xor和,然后cur变为其父亲(也就是cur所在链的链顶的父亲),prev变为cur,由此不断循环,直到cur成为原图树根所在的Splay树的树根。

    “经常用到的操作”Access后Splay

    首先Access(cur),然后Splay(cur)。此时cur成为了顶端Splay树根。因为原来原图的树根在当前splay树的最左侧,cur在splay树的树根,还没有右孩子,所以说这个操作使得原图中的树根成为了链底,cur成为了链顶。

    MakeRoot

    MakeRoot(Node *cur)是将结点cur设为原图中的根节点。

    “经常用到的操作”后,顶端Splay树上的节点里原根越近,离新根就越远。所以将顶端Splay树整体翻转即可。

    关于翻转

    翻转就是将自己的左右孩子交换,然后将自己的翻转标记取反。每次Splay之前都要PushDown,递归到当前Splay树的根节点,从根节点开始,如果当前结点有有Rev标记,则将当前结点的孩子的左右孩子交换。

    MakePath

    MakePath(Node *from, Node *to)是将from和to放到一个链内,并使to成为链顶,from成为链底。首先MakeRoot(from),然后“经常用到的操作”to即可。

    FindRoot

    FindRoot(Node *cur)指以cur为起点,找到原图的树根。“经常用到的操作”cur,然后不断找左孩子即可。

    应用操作

    Query

    Query(Node *from, Node *to)是要查询from到to的路径的Xor和。MakePath(from, to)后,因为splay树根节点的Xor值便是整条链的Xor和,所以to的Xor值即为所求。

    Link

    Link(Node *from, Node *to)是要连接from和to。首先MakeRoot(from),然后如果FindRoot(to)!=from,则将from的Father设成to即可。(注意必须是MakeRoot(from),而不是“经常用到的操作”from,因为这样并没有给father设置,而是给链的根设置了)。

    Cut

    Cut(Node *from, Node *to)是要剪掉from和to。首先MakePath(from, to),如果from和to已经连接,则两点间的路径就是两点的边,又因为MakePath后to是链顶,所以如果to->LeftSon==from,再Cut。

    Modify

    Modify(Node *cur)是要修改cur的值。先把cur Splay到其所在Splay树的树根,然后改cur的值并Refresh。这样避免了对整个树进行更新。

    #include <cstdio>
    #include <cstring>
    #include <cassert>
    #include <algorithm>
    using namespace std;
    
    const int MAX_NODE = 300010;
    #define LOOP(i, n) for(int i=1; i<=n; i++)
    
    struct LctTree
    {
    private:
    	struct Node
    	{
    		Node *Father, *LeftSon, *RightSon;
    		int Weight, Xor, Id;
    		bool ReverseFlag;
    
    		Node()
    		{
    			Weight = Xor = 0;
    			LeftSon = RightSon = Father = NULL;
    			ReverseFlag = false;
    		}
    
    		Node(int weight) :Weight(weight), Xor(weight)
    		{
    			LeftSon = RightSon = Father = NULL;
    			ReverseFlag = false;
    		}
    
    		void Reverse()
    		{
    			swap(LeftSon, RightSon);
    			ReverseFlag = !ReverseFlag;
    		}
    
    		void PushDown()
    		{
    			if (ReverseFlag)
    			{
    				if (LeftSon)//不要忘了判断
    					LeftSon->Reverse();
    				if (RightSon)//不要忘了判断
    					RightSon->Reverse();
    				ReverseFlag = false;
    			}
    		}
    
    		bool IsRoot()
    		{
    			return Father == NULL || (Father->LeftSon != this && Father->RightSon != this);
    		}
    
    		bool IsLeftSon()
    		{
    			return Father->LeftSon == this;
    		}
    
    		void Refresh()
    		{
    			Xor = (LeftSon ? LeftSon->Xor : 0) ^ (RightSon ? RightSon->Xor : 0) ^ Weight;
    		}
    	}_nodes[MAX_NODE];
    
    	struct SplayTree
    	{
    	private:
    		Node *InnerRoot;
    
    		void PushDown(Node *cur)
    		{
    			if (!cur->IsRoot())
    				PushDown(cur->Father);
    			cur->PushDown();
    		}
    
    		void Rotate(Node *cur)
    		{
    			Node *gfa = cur->Father->Father;
    			Node **gfaSon = cur->Father->IsRoot() ? &InnerRoot : (cur->Father->IsLeftSon() ? &gfa->LeftSon : &gfa->RightSon);//TODO
    			Node **faSon = cur->IsLeftSon() ? &cur->Father->LeftSon : &cur->Father->RightSon;
    			Node **curSon = cur->IsLeftSon() ? &cur->RightSon : &cur->LeftSon;
    			*faSon = *curSon;
    			if (*faSon)
    				(*faSon)->Father = cur->Father;
    			*curSon = cur->Father;
    			(*curSon)->Father = cur;
    			*gfaSon = cur;
    			(*gfaSon)->Father = gfa;
    			(*curSon)->Refresh();
    			cur->Refresh();
    		}
    
    	public:
    		void Splay(Node *cur)
    		{
    			PushDown(cur);
    			while (!cur->IsRoot())
    			{
    				if (!cur->Father->IsRoot())
    					Rotate(cur->Father->IsLeftSon() == cur->IsLeftSon() ? cur->Father : cur);
    				Rotate(cur);
    			}
    		}
    	}AuTree;
    
    	void Access(Node *cur)
    	{
    		Node *prev = NULL;
    		while (cur)//是cur,不是cur->IsRoot()。
    		{
    			AuTree.Splay(cur);
    			cur->RightSon = prev;
    			cur->Refresh();
    			prev = cur;
    			cur = cur->Father;
    		}
    	}
    
    	Node *FindRoot(Node *cur)
    	{
    		Access(cur);
    		AuTree.Splay(cur);
    		while (cur->LeftSon)
    			cur = cur->LeftSon;
    		return cur;
    	}
    
    	void MakeRoot(Node *cur)
    	{
    		Access(cur);
    		AuTree.Splay(cur);
    		cur->Reverse();
    	}
    
    	void MakePath(Node *from, Node *to)
    	{
    		MakeRoot(from);
    		Access(to);
    		AuTree.Splay(to);
    	}
    
    	int Query(Node *from, Node *to)
    	{
    		MakePath(from, to);
    		return to->Xor;
    	}
    
    	void Link(Node *from, Node *to)
    	{
    		MakeRoot(from);
    		if (FindRoot(to) != from)
    			from->Father = to;
    	}
    
    	void Cut(Node *from, Node *to)
    	{
    		MakePath(from, to);
    		if (to->LeftSon == from)
    			from->Father = to->LeftSon = NULL;
    	}
    
    	void Modify(Node *cur, int w)
    	{
    		Access(cur);
    		AuTree.Splay(cur);
    		cur->Weight = w;
    		cur->Refresh();
    	}
    
    public:
    	void SetNode(int id, int weight)
    	{
    		Node *cur = _nodes + id;
    		cur->Id = id;
    		cur->Weight = cur->Xor = weight;//易忘点:cur->Xor
    	}
    
    	int Query(int u, int v)
    	{
    		return Query(_nodes + u, _nodes + v);
    	}
    
    	void Link(int u, int v)
    	{
    		Link(_nodes + u, _nodes + v);
    	}
    
    	void Cut(int u, int v)
    	{
    		Cut(_nodes + u, _nodes + v);//TODO//别写成Link了
    	}
    
    	void Modify(int u, int w)
    	{
    		Modify(_nodes + u, w);
    	}
    }g;
    
    int main()
    {
    	int nodeCnt, opCnt, w, op, u, v;
    	scanf("%d%d", &nodeCnt, &opCnt);
    	LOOP(i, nodeCnt)
    	{
    		scanf("%d", &w);
    		g.SetNode(i, w);
    	}
    	while (opCnt--)
    	{
    		scanf("%d", &op);
    		switch (op)
    		{
    		case 0://Query
    			scanf("%d%d", &u, &v);
    			printf("%d
    ", g.Query(u, v));
    			break;
    		case 1://Link
    			scanf("%d%d", &u, &v);
    			g.Link(u, v);
    			break;
    		case 2://Cut
    			scanf("%d%d", &u, &v);
    			g.Cut(u, v);
    			break;
    		case 3://Modify
    			scanf("%d%d", &u, &w);
    			g.Modify(u, w);
    			break;
    		}
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    吴军博士:物联网和人工智能将再造一个英特尔和微软 | 万物互联
    速来膜拜!20位活跃在Github上的国内技术大牛
    创建带Mipmap的osg::Image
    C#文件系统管理【转】
    C#文本文件(.txt)读写 [转]
    C#连接SQL Server数据库进行简单操作[转]
    shell脚本把一些请求量非常高的ip给拒绝掉
    linux获取精准进程PID之pgrep命令
    Kubernetes的Cron Job
    StatefulSet和Deployment的区别
  • 原文地址:https://www.cnblogs.com/headboy2002/p/8643987.html
Copyright © 2020-2023  润新知