zoukankan      html  css  js  c++  java
  • 动态树(Link-Cut-Tree)简单总结(指针版本)

    Link-Cut-Tree(动态树 LCT)


    1.定义

    1. 偏爱子节点: 一颗子树内最后访问的点若在该子树根节点 X 的某个儿子节点 P 的子树中,则称 P 为 X 的偏爱子节点。
    2. 偏爱边:连向偏爱子节点的边。
    3. 偏爱路径:一条全为偏爱边构成的路径(一定是一条链,类似于树链剖分的重链)
    4. 每一条偏爱路径(以下直接写做重链)都由一棵Splay来进行维护.
    注意:

    1. LCT不一定是二叉树。
    2. 若 p->fa==NULL 则p为其所在 LCT 的树根
      3. Splay -> 路径(且一定是树上的一条链)
      4. LCT -> 一棵树(由多棵Splay构成的森林)
      5. 整张图 -> 多棵 LCT 构成的森林(好几片Splay构成的森林)
      6. Splay作为辅助树,改变的只是 LCT 中的轻重边关系
      7. 基本的结构体变量
    struct node{
    	node* fa;node* son[2];
    	bool rev,is_root;//区间反转标记和是否是Splay的根
    }
    

    2.基本操作

    代码均使用指针。
    可自己画图以理解。

    注:以下代码中有:

    #define __ NULL
    #define ls son[0]
    #define rs son[1]
    #define get_son(a) (a->fa->rs==a)
    
    1. Splay的操作(为了适应LCT有些改动)
    void rotate(node* p)
    {
    	if(p->is_root) return;
    	register node* q=p->fa;register node* gp=q->fa;
    	register int k=get_son(p);
    	q->son[k]=p->son[k^1];
    	if(p->son[k^1]!=__) p->son[k^1]->fa=q;
    	p->fa=gp;
    	if(q->is_root) q->is_root=0,p->is_root=1;
    	//由于不能影响到另一棵Splay,要用 is_root 判断
    	else if(gp!=__) gp->son[get_son(q)]=p;
    	q->fa=p;p->son[k^1]=q;//记住旋转时指针指向的变更顺序!!
    	return;
    }
    void Splay(node* p)
    {
    	if(p==__) return;
    	push(p);//在Splay到根之前要先下传标记(区间反转)
    	while(!p->is_root){//别旋出去啦!
    		if(p->fa->is_root) {rotate(p);break;}
    		if(get_son(p)==get_son(p->fa)) rotate(p->fa);
    		else rotate(p);rotate(p);
    	}
    	return ;
    }
    

    2.(access(p)) 访问某节点

    void access(node* p)//访问某节点 p ,单独开辟一条路到节点 p ;
    {
    	register node* pre=__;
    	while(p!=__){
    		Splay(p);
    		if(p->rs!=__) p->rs->is_root=1;
    		p->rs=pre;//切断右子树(不同Splay的父亲记录只从子到父)
    		if(pre!=__) pre->is_root=0;
    		//pre一定是上一棵Splay的根,要把它加到另一棵Splay中;
    		//updata(p);(更新)
    		pre=p;p=p->fa;
    	}
    }
    

    3.(makeroot(p)) 使p变为其所在LCT的根节点

    void m_root(node* p)//使某个点成为根
    {
    	if(p==__) return;
    	access(p);Splay(p);//连一条从树根到 p 的路径
    	p->rev^=1;
    /*
    因为树根的深度应要是最小的,上面操作只是让 p 成为了原树根所在Splay的根,
    但他仅有左子树,深度最大,故还应该反转该Splay,使得 p 成为真正的根
    (即保证深度为整棵LCT中深度最小的点);
    
    上述操作并不会改变原树的结构
    */
    }
    

    4.(Link(u,v)) 连接u,v所在的LCT,使之变为一棵LCT

    void Link(node* p,node* q)
    {
    	m_root(p);//不改变p所在树的结构,只是将p提取出来便于操作
    	p->fa=q;//连接了两棵LCT改变了原图的结构(连的边是轻边,不用updata)
    }
    

    5.(split(u,v)) 分离出u->v这条路径,其上的点都在一颗Splay中,可迅速获得路径上的信息

    void split(node* p,node* q){
        m_ root(p);
        access(q);
        Splay(q);
    }
    

    6.(Cut(u,v))砍断边((u,v))分成两棵LCT

    void Cut(node* p,node* q)
    {
    	split(p,q);
    	if(q->ls==p) p->fa=q->ls=__,p->is_root=1;
    	//此时q所在Splay仅有他一个节点
    }
    

    6.标记的下放

    void push_down(node* p)
    {
    	if(p==__||p->rev==0) return;
    	p->rev=0;
    	if(p->ls!=__) p->ls->rev^=1;if(p->rs!=__) p->rs->rev^=1;
    	swap(p->ls,p->rs);
    }
    void push(node* p){//数据不大时可写成递归版本
    	h=1;st[h]=p;
    	while(!p->is_root) st[++h]=(p=p->fa);
    	for(int i=h;i;i--) push_down(st[i]);
    }
    void push(node* p)//递归版本
    {
        if(p==__) return;
        if(p->is_root==0) push(p->fa);
        push_down(p);
    }
    

    7.(find(p)) 返回p所在LCT的根节点(通常用于判断两点是否联通)

    node* find(node* p)
    {
        for(access(p),Splay(p);p->ls!=__;p=p->ls);
        return p;
    }
    

    3.LCT的应用

    LCT可以很方便的提取出两点之间的路径和改变原树的形态,因此在处理一些树上的路径问题非常行之有效。

    1.动态询问两点之间的连通性
    例题.LuoguP2147 [SDOI2008]Cave 洞穴勘测 用find操作即可

    2.动态询问树上两点间的距离
    例题1.BZOJ2002: [Hnoi2010]Bounce 弹飞绵羊
    题解
    例题2.LuoguP3721 [AH2017/HNOI2017]单旋
    题解

    3.各种树上路径问题
    例题1.BZOJ3669: [Noi2014]魔法森林 动态维护最小生成树
    题解

    持续更新...

  • 相关阅读:
    LeetCode#34 Search for a Range
    Multiplication algorithm
    LeetCode#31 Next Permutation
    Spring boot之Hello World
    spring boot 简介
    分布式-网络通信-线程
    分布式-网络通信-协议
    分布式-架构图
    9.leetcode70-climbing stairs
    8.Leetcode69 Sqrt(x) 笔记
  • 原文地址:https://www.cnblogs.com/NeosKnight/p/10391208.html
Copyright © 2011-2022 走看看