zoukankan      html  css  js  c++  java
  • 【Qtree】Query on a tree系列LCT解法

    Qtree1-7

    Qtree1 裸的树链剖分,当然也可以用LCT写,就不说什么了...

    Qtree2 倍增lca,当然也可以用LCT写,就不说什么了...

    Qtree3 裸的树链剖分,当然也可以用LCT写,就不说什么了...

    接下来的Qtree4-7才是重点

    推荐顺序(7->6->4->5),因为写了7就可以改一改贴到6,写了4就可以改一改贴到5了,瞬间少写两题

    Qtree4 我已经想到了一个很好的LCT写法,只可惜这里空白太小,写不下了

    Qtree5更简单一些,推荐先写5,或者写这题的不带边权版bzoj1095


    —————————————————————————分割线——————————————————————————————


    这题的做法很多,有动态树分治,树分治和其他的算法

    我的写法是受到Qtree7的启发,听说Qtree7LCT的做法可以扩展到这题,于是就写了一发


    这题用到的是Qtree7的两个思想之一,就是用一个数据结构维护虚边连接的子树的信息。

    这题是用set维护。


    首先把边权附到儿子结点上,记为len(len会引发很多细节问题,一定要小心)

    实边上的信息就维护一个lmax,rmax,maxs,sum,类似线段树维护最长连续0/1串的长度

    虚边就类似点分治时的想法,记录只到x点的链,和x子树中已经构成路径的两个白点(只管虚子树)


    但是定义有一些区别

    lmax这个子splay表示的一段重链最浅的白点出发的最长链长,rmax从该实链最深的白点出发的最长链长,maxs即答案,包括实边虚边的最远两白点距离 ,sum表示该子splay段实链总长

    为了叙述方便,其他的一些定义:ls左儿子,rs有儿子


    先讨论虚子树对maxs的影响,实边要复杂一些

    最长和次长到x的链可以合并成一条路径

    子树里已经形成的最大路径也可以更新

    如果x是白点,那么最长链就可以更新了


    虚子树信息的维护主要是在access中的虚实切换

    对于要从实变虚的原右儿子,maxs[rs]扔进路径的set里,lmax[rs]扔进链的set里,因为要到x点,所以是lmax

    同样,从虚变实的新右儿子,把maxs[y]从set里删去,把lmax[y]从set里删去

    虚部就完成了



    对于实链我们可以发现一个性质,LCT中splay的任意一个子树,对应的都是原树中实链连续的一段

    我们在线段树的update合并左右区间时不可能向左向右暴力找到最远的1,更新答案

    同样,我们也不能在splay里找到原树中紧邻x上面的点,把它splay上来更新答案

    但一个合法的路径要穿过x点,就要过紧邻x上面的点,和紧邻x下面的点

    所以我们要记lmax,rmax


    然后就是写到吐血激动人心的update了

    先更新lmax和rmax

    lmax要过整棵子splay的最浅点

    那么有lmax[x]=max(lmax[ls]//不过x

    ,max(虚链中最长+整个左儿子代表的一段,lmax[rs]+整个左儿子代表的一段))


    rmax同理,注意x对应的这条边,会有一些细节问题


    于是maxs就又有了两种新的更新方式

    maxs[x]=max(maxs[x],rmax[rs]+max(虚链,lmax[rs]))

    maxs[x]=max(maxs[x],lmax[rs]+max(虚链,rmax[ls]))

    然后更新方式就完了

    然后就没有然后了


    O(nlog^2)卡常警报

    不过当set的size比较大时,说明虚边很多,树的深度不深,LCT的操作次数要少一些

    如果树的深度深,那么set的size又比较小,所以两个log并不严格

    #include<bits/stdc++>
    const int maxn=200010,maxm=200010,inf=1e9;
    using namespace std;
    int n,Q,pre[maxm],now[maxn],son[maxm],val[maxm],tot,ans=-inf,col[maxn];char op[5];//0白1黑
    void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}
    inline int fir(multiset<int> &s){return s.size()?*s.rbegin():-inf;}//最大,因为c++是左闭右开,所以s.end()不是最后一个,要--s.end()才是
    inline int sec(multiset<int> &s){return s.size()>1?*(++s.rbegin()):-inf;}
    
    struct Tlct{
    	#define ls ch[x][0]
    	#define rs ch[x][1]
    	int ch[maxn][2],fa[maxn],lmax[maxn],rmax[maxn],maxs[maxn],sum[maxn],len[maxn],w[maxn];
    	//lmax这个子splay表示的一段重链最浅的点出发的最长链长,rmax从该实链最深的点出发的最长链长,maxs即答案,最远两白点距离 
    	//len当前点和父亲之间的边长度,sum该段实链总长 w表示颜色 白色为0表示有一个白点,距离可以是0,黑色为-inf,表示无白点
    	multiset<int> chain[maxn],path[maxn];//chain 链 path 路径 用来存虚边信息
    	inline int which(int x){return ch[fa[x]][1]==x;}
    	inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    	void init(){for (int i=0;i<=n;i++) lmax[i]=rmax[i]=maxs[i]=-inf;}
    	void update(int x){
    		assert(x);
    		sum[x]=sum[ls]+sum[rs]+len[x];
    		int cha=max(w[x],fir(chain[x]));
    		int L=max(cha,rmax[ls]+len[x]);//从左子树(实链中更浅的点)或虚边的白点来的最远链长
    		int R=max(cha,lmax[rs]);//同理
    		lmax[x]=max(lmax[ls],sum[ls]+len[x]+R);//要想清楚加不加len[x]
    		rmax[x]=max(rmax[rs],sum[rs]+L);
    		
    		maxs[x]=max(rmax[ls]+len[x]+R,lmax[rs]+L);
    		maxs[x]=max(maxs[x],max(maxs[ls],maxs[rs]));
    		maxs[x]=max(maxs[x],fir(path[x]));
    		maxs[x]=max(maxs[x],fir(chain[x])+sec(chain[x]));
    		if (w[x]==0) maxs[x]=max(max(maxs[x],fir(chain[x])),0);//如果这是白点,就可以用子树中的链更新
    	}
    	void rotate(int x){
    		assert(x);
    		int y=fa[x],z=fa[y],nx=which(x),ny=which(y);
    		fa[ch[x][!nx]]=y,ch[y][nx]=ch[x][!nx];
    		fa[x]=z;if (!isroot(y)) ch[z][ny]=x;
    		fa[y]=x,ch[x][!nx]=y;update(y);
    	}
    	void splay(int x){
    		for (;!isroot(x);){
    			int y=fa[x];
    			if (isroot(y)) rotate(x);
    			else if (which(x)==which(y)) rotate(y),rotate(x);
    			else rotate(x),rotate(x);
    		}update(x);
    	}
    	void access(int x){
    		for (int y=0;x;y=x,x=fa[x]){
    			splay(x);
    			if (rs) path[x].insert(maxs[rs]),chain[x].insert(lmax[rs]);
    			if (y) path[x].erase(path[x].find(maxs[y])),chain[x].erase(chain[x].find(lmax[y]));
    			rs=y,update(x);
    		}
    	}
    	void modify(int x){
    		access(x),splay(x);
    		col[x]^=1,w[x]=!col[x]?0:-inf;
    		update(x),ans=maxs[x];
    		//for (int i=1;i<=3;i++) print(i);
    	}
    	void print(int x){printf("x%d fa%d ls%d rs%d lmax%d rmax%d maxs%d sum%d len%d
    ",x,fa[x],ls,rs,lmax[x],rmax[x],maxs[x],sum[x],len[x]);}
    }T;
    
    void dfs(int x){
    	for (int y=now[x];y;y=pre[y]) if (son[y]!=T.fa[x]){
    		T.fa[son[y]]=x,T.len[son[y]]=val[y],dfs(son[y]);
    		T.chain[x].insert(T.lmax[son[y]]),T.path[x].insert(T.maxs[son[y]]);
    	}T.update(x);
    }
    
    int main(){
    	scanf("%d",&n),T.init();
    	for (int i=1,x,y,z;i<n;i++) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
    	dfs(1),ans=T.maxs[1],scanf("%d",&Q);
    	for (int i=1,x;i<=Q;i++){
    		scanf("%s",op+1);
    		if (op[1]=='A'){
    			if (ans<0) puts("They have disappeared.");
    			else printf("%d
    ",ans);
    		}
    		else scanf("%d",&x),T.modify(x);
    	}
    	return 0;
    }
    
    /*
    6
    1 2 1
    1 3 1
    3 4 2
    3 5 2
    3 6 2
    7
    A
    C 4
    C 5
    C 6
    A
    */
    


    Qtree5

    qtree4的弱化版,注意初始都是是黑点,求的是最近


    Qtree6 

    很显然,树链剖分是可捉此题的

    开两棵树,一棵白,一棵黑,记siz[x][0/1]表示子树中还有多少与之连通的同色点

    再维护dfs序最小的连通的同色点(线段树里就记最左边的1的位置)

    修改颜色时,把x到该点的路径上,重链就区间减,轻链就暴力减


    ——————————————————————————LCT写法————————————————————————————


    也是记两棵树,一棵黑一颗白,对于虚边的siz直接用上面的方法,新开一个siz数组维护一下就好了

    但是还有一个问题没有解决,如果改色时暴力link-cut,显然菊花图时修改的边数可以达到O(n)级别


    于是就有了一个巧妙的写法,当一个点从白变黑时,只在白树里cut掉它和父亲的边,只在黑树里link上它和父亲的边

    也就是保证黑白树中的每条边的儿子一定是黑/白色的,父亲则可能是其他颜色

    询问时找到最浅的点,白点就在白树里询问,黑点就在黑树里询问,但要注意根要特判


    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    const int maxn=100010,maxm=200010;
    using namespace std;
    int n,Q,pre[maxm],now[maxn],son[maxm],tot,f[maxn],col[maxn];char ch;
    void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}
    void read(int &x){
        for (ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
        int t;if (ch=='-') t=-1,ch=getchar();else t=1;
        for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
        x=x*t;
    }
    
    struct Tlct{
    	#define ls ch[x][0]
    	#define rs ch[x][1]
    	int fa[maxn],ch[maxn][2],siz[maxn],s[maxn];
    	inline int which(int x){return ch[fa[x]][1]==x;}
    	inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    	void update(int x){siz[x]=siz[ls]+siz[rs]+s[x]+1;}
    	void rotate(int x){
    		int y=fa[x],z=fa[y],nx=which(x),ny=which(y);
    		fa[ch[x][!nx]]=y,ch[y][nx]=ch[x][!nx];
    		fa[x]=z;if (!isroot(y)) ch[z][ny]=x;
    		fa[y]=x,ch[x][!nx]=y;update(y);
    	}
    	void splay(int x){
    		for (;!isroot(x);){
    			int y=fa[x];
    			if (isroot(y)) rotate(x);
    			else if (which(x)==which(y)) rotate(y),rotate(x);
    			else rotate(x),rotate(x);
    		}update(x);
    	}
    	void access(int x){
    		for (int y=0;x;y=x,x=fa[x]){
    			splay(x);
    			if (rs) s[x]+=siz[rs];
    			if (y) s[x]-=siz[y];
    			rs=y,update(x);
    		}
    	}
    	void link(int x){
    		access(f[x]),splay(f[x]),splay(x);
    		ch[f[x]][1]=x,fa[x]=f[x],update(f[x]);
    	}
    	void cut(int x){access(x),splay(x),fa[ls]=0,ls=0,update(x);}
    	int getroot(int x){
    		access(x),splay(x);
    		while (ls) x=ls;
    		return x;
    	}
    	int query(int x){
    		int c=col[x];x=getroot(x),splay(x);
    		return col[x]==c?siz[x]:siz[rs];
    	}
    }T[2];
    
    void dfs(int x){
    	for (int y=now[x];y;y=pre[y]) if (son[y]!=f[x]){
    		f[son[y]]=T[col[son[y]]].fa[son[y]]=x;
    		dfs(son[y]),T[col[son[y]]].s[x]+=T[col[son[y]]].siz[son[y]];
    	}
    	T[0].update(x),T[1].update(x);
    }
    
    int main(){
    	scanf("%d",&n);
    	for (int i=1,x,y;i<n;i++) read(x),read(y),add(x,y),add(y,x);
    	dfs(1),read(Q);
    	for (int i=1,op,x;i<=Q;i++){
    		read(op),read(x);
    		if (!op) printf("%d
    ",T[col[x]].query(x));
    		else{
    			if (f[x]) T[col[x]].cut(x),T[col[x]^1].link(x);
    			col[x]^=1;
    		}
    	}
    	return 0;
    }


    Qtree7


    和Qtree6类似,维护虚边信息用set,因为维护最大值不能像维护siz一样直接加和减,所以要开一个set,细节稍微多一点

    #include<set>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    const int maxn=100010,maxm=200010;
    using namespace std;
    int n,Q,pre[maxm],now[maxn],son[maxm],tot,col[maxn],f[maxn];char ch;
    void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}
    void read(int &x){
        for (ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
        int t;if (ch=='-') t=-1,ch=getchar();else t=1;
        for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
        x=x*t;
    }
     
    struct Tlct{
        #define ls ch[x][0]
        #define rs ch[x][1]
        int ch[maxn][2],fa[maxn],maxs[maxn],val[maxn];multiset<int> s[maxn];
        inline bool isroot(int x){return (ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x);}
        inline bool which(int x){return ch[fa[x]][1]==x;}
        inline void update(int x){
            maxs[x]=val[x];
            if (s[x].size()) maxs[x]=max(maxs[x],*s[x].rbegin());
            if (ls) maxs[x]=max(maxs[x],maxs[ls]);
            if (rs) maxs[x]=max(maxs[x],maxs[rs]);
        }
        void rotate(int x){
            int y=fa[x],z=fa[y],nx=which(x),ny=which(y);
            fa[ch[x][!nx]]=y,ch[y][nx]=ch[x][!nx];
            fa[x]=z;if (!isroot(y)) ch[z][ny]=x;
            fa[y]=x,ch[x][!nx]=y;update(y);
        }
        void splay(int x){
            for (;!isroot(x);){
                int y=fa[x];
                if (isroot(y)) rotate(x);
                else if (which(x)==which(y)) rotate(y),rotate(x);
                else rotate(x),rotate(x);
            }
            update(x);
        }
        void access(int x){
            for (int y=0;x;y=x,x=fa[x]){
                splay(x);
                if (rs) s[x].insert(maxs[rs]);
                rs=y;
                if (y) s[x].erase(s[x].find(maxs[y]));
                update(x);
            }
        }
        int getroot(int x){
            access(x),splay(x);
            while (ls) x=ls;
            return x;
        }
        void link(int x){
            access(f[x]),splay(f[x]),splay(x);
            ch[f[x]][1]=x,fa[x]=f[x],update(f[x]);
        }
        void cut(int x){access(x),splay(x),fa[ls]=0,ls=0,update(x);}
    	void modify(int x,int v){access(x),splay(x),val[x]=v,update(x);}
        int query(int x){
    		int c=col[x];x=getroot(x),splay(x);
    		return c==col[x]?maxs[x]:maxs[rs];
    	}
    }T[2];
    
    void dfs(int x){
        for (int y=now[x];y;y=pre[y]) if (son[y]!=f[x]){
            f[son[y]]=T[col[son[y]]].fa[son[y]]=x;
            dfs(son[y]),T[col[son[y]]].s[x].insert(T[col[son[y]]].maxs[son[y]]);
        }
        T[0].update(x),T[1].update(x);
    }
    
    int main(){
        scanf("%d",&n);
        for (int i=1,x,y;i<n;i++) read(x),read(y),add(x,y),add(y,x);
        for (int i=1;i<=n;i++) read(col[i]);
    	for (int i=1;i<=n;i++) read(T[0].val[i]),T[1].val[i]=T[0].val[i];
    	dfs(1),read(Q);
    	for (int i=1,op,x,y;i<=Q;i++){
    		read(op),read(x);
    		if (op==0) printf("%d
    ",T[col[x]].query(x));
    		if (op==1){
    			if (f[x]) T[col[x]].cut(x),T[col[x]^1].link(x);
    			col[x]^=1;
    		}
    		if (op==2) read(y),T[0].modify(x,y),T[1].modify(x,y);
    	}
    	return 0;
    }



  • 相关阅读:
    java中创建线程的方式
    idea查看一个接口的子接口或实现类的快捷键
    idea查看源码没有注释的问题
    spring中的Aop
    spring中的ApplicationListener监听器
    spring中的事务管理
    IDEA创建springboot 项目
    xiaopiu产品设计
    java 6大设计原则 一:观察者模式
    java面向对象
  • 原文地址:https://www.cnblogs.com/thythy/p/5493644.html
Copyright © 2011-2022 走看看