zoukankan      html  css  js  c++  java
  • BZOJ4573: [Zjoi2016]大森林

    BZOJ4573: [Zjoi2016]大森林

    Description

    小Y家里有一个大森林,里面有n棵树,编号从1到n。
    一开始这些树都只是树苗,只有一个节点,标号为1。
    这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。
    小Y掌握了一种魔法,能让第l棵树到第r棵树的生长节点长出一个子节点。
    同时她还能修改第l棵树到第r棵树的生长节点。
    她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?

    Input

    第一行包含 2 个正整数 n,m,共有 n 棵树和 m 个操作。
    接下来 m 行,每行包含若干非负整数表示一个操作,操作格式为:
    0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例如,第一个 0 号操作产生的子节点标号为 2), l 到 r 之间的树长出的节点标号都相同。保证 1≤l≤
    r≤n 。
    1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (l≤i≤r)这棵树,如果标号 x的点不在其中,那么这个操作对该树不产生影响。保证 1≤l≤r≤n , x 不超过当前所有树中节点最大的标号。
    2 x u v 询问第 x 棵树中节点 u 到节点 v 点的距离,也就是在第 x 棵树中从节点 u 和节点 v 的最短路上边的数量。保证1≤x≤n,这棵树中节点 u 和节点 v 存在。
    N<=10^5,M<=2*10^5

    Output

    输出包括若干行,按顺序对于每个小Y的询问输出答案

    Sample Input

    5 5
    0 1 5
    1 2 4 2
    0 1 4
    2 1 1 3
    2 2 1 3

    Sample Output

    1
    2

    题解Here!

    首先理解题目要我们干什么。
    区间种树。。。
    感觉跟$LCT$跑不了关系。。。
    但是肯定不能暴力种树对吧。。。
    所以我们要简化问题。
    我们发现,如果没有操作$1$,那么种出来的所有的树一定都是一种形态——一条链
    所以我们考虑操作$1$的影响。
    我们发现,对于一个操作$1$来说,假设询问长这样:$l r x$
    那么区间$[l,r]$之内的树的生长节点一定是一样的。
    而改变就在$[l-1,l],[r,r+1]$这两个区间。
    $[l-1,l]$表示从$l$开始,生长节点变成了$x$。
    $[r,r+1]$表示从$r+1$开始,生长节点不变。
    现在假设以后都没有操作$1$,那么我们又可以换一种理解:
    $[l-1,l]$表示从$l$开始,以后所长出来的所有节点全部嫁接到了$x$下面。
    $[r,r+1]$表示从$r+1$开始,以后所长出来的所有节点还是嫁接在原来的生长节点下面。
    所以我们只要快速嫁接子树即可。
    但是,我们考虑一个问题——总不能暴力一个一个地嫁接吧。。。
    题目很好,没有说强制在线——那就离线
    然后我们还有最后一个问题——如何快速嫁接子树?
    我们可以想到,如果这个嫁接是$O(1)$的该有多好!
    $O(1)$的嫁接是什么?——直接改父亲指向!
    自然,我们想到了一个可以直接改父亲指向的东东——虚树
    我们对于每个操作$1$,我们建立一个虚点,将所有后面的操作$0$生成的子树全部挂在这个虚点下面。
    这样每次嫁接我们就可以直接断开原来的边,连上新边。
    复杂度虽然不是$O(1)$的,但是$O(log_2n)$已经很优秀了。
    但是我们发现这样还是要暴力一个一个嫁接,复杂度依然爆炸。。。
    没事,我们上面不是说了操作$1$的另外一种理解吗?
    所以我们可以把每个操作$1$都拆成两个嫁接操作。
    再对所有操作以端点为第一关键字,原时间顺序为第二关键字排序。
    然后一遍扫描线扫过去就可以了。
    到此,操作$1$分析完毕!
    等等。。。好像还有个操作$2$啊喂!
    我们不是建出来了$n$颗树嘛?
    那就直接丢到树上就好了嘛。。。
    还记得树上两点的距离公式吗?
    $$dis(u,v)=deep[u]+deep[v]-2 imes deep[LCA(u,v)]$$
    然后。。。等等,这不是在$LCT$上吗?
    于是问题来了——$LCT$怎么求$LCA$啊???
    其实很简单,假如我们要求$LCA(x,y)$,我们先$access(x);splay(x);$,然后$access(y);$。
    我们发现这个时候的树根就是$LCA(x,y)$!(我可能说错了,各路神犇轻喷。。。)
    大概意思的话,可以看代码,$work$函数里面的$lca$就表示$LCA$。
    可以画几个图感性理解一下。
    但是这个$deep[i]$怎么搞啊。。。
    我们发现,我们用上述方法求出来的$LCA$可能是个虚点。
    那个对应的实点可能不一定是$LCA$。。。
    怎么办?
    我们给个点权就好——实点点权为$1$,虚点点权为$0$。
    记$s[x]=s[lson]+s[rson]+v[x]$,$v[x]$表示$x$的点权。
    那么最终的答案就可以表示为:
    $$dis(u,v)=s[u]+s[v]-2 imes s[LCA(u,v)]$$
    为什么不用$deep[i]$了呢?因为我们把重复的那部分已经减掉了。
    这样就避免了复杂的求$deep[i]$。
    到此,我们对于整个题目的分析结束。
    剩下的就是代码的事儿了。。。
    反正就是各种各样奇奇怪怪乱七八糟的变量名和映射然后毁天灭地。。。
    我还开了个$namespace$防止变量名重复。。。
    吐槽:题目好难。。。代码好烦。。。打字好累。。。
    附代码:
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #define MAXN 1000010
    using namespace std;
    int n,m,q=0,num=1;
    int s[MAXN],t[MAXN],w[MAXN],ans[MAXN];
    bool Ask[MAXN];
    struct Question{
    	int f,x,y,id;
    	friend bool operator <(const Question &p,const Question &q){
    		if(p.id==q.id)return p.f<q.f;
    		return p.id<q.id;
    	}
    }que[MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    namespace LCT{
    	int size=0,stack[MAXN];
    	struct Link_Cut_Tree{
    		int f,flag,son[2];
    		int v,s;
    	}a[MAXN];
    	inline bool isroot(int rt){
    		return a[a[rt].f].son[0]!=rt&&a[a[rt].f].son[1]!=rt;
    	}
    	inline int newnode(int v){
    		int rt=++size;
    		a[rt].s=a[rt].v=v;
    		return rt;
    	}
    	inline void pushup(int rt){
    		if(!rt)return;
    		a[rt].s=a[a[rt].son[0]].s+a[a[rt].son[1]].s+a[rt].v;
    	}
    	inline void pushdown(int rt){
    		if(!rt||!a[rt].flag)return;
    		a[a[rt].son[0]].flag^=1;a[a[rt].son[1]].flag^=1;a[rt].flag^=1;
    		swap(a[rt].son[0],a[rt].son[1]);
    	}
    	inline void turn(int rt){
    		int x=a[rt].f,y=a[x].f,k=a[x].son[0]==rt?1:0;
    		if(!isroot(x)){
    			if(a[y].son[0]==x)a[y].son[0]=rt;
    			else a[y].son[1]=rt;
    		}
    		a[rt].f=y;a[x].f=rt;a[a[rt].son[k]].f=x;
    		a[x].son[k^1]=a[rt].son[k];a[rt].son[k]=x;
    		pushup(x);pushup(rt);
    	}
    	void splay(int rt){
    		int top=0;
    		stack[++top]=rt;
    		for(int i=rt;!isroot(i);i=a[i].f)stack[++top]=a[i].f;
    		while(top)pushdown(stack[top--]);
    		while(!isroot(rt)){
    			int x=a[rt].f,y=a[x].f;
    			if(!isroot(x)){
    				if((a[y].son[0]==x)^(a[x].son[0]==rt))turn(rt);
    				else turn(x);
    			}
    			turn(rt);
    		}
    	}
    	void access(int rt){
    		for(int i=0;rt;i=rt,rt=a[rt].f){
    			splay(rt);
    			a[rt].son[1]=i;
    			pushup(rt);
    		}
    	}
    	int access_lca(int rt){
    		int i;
    		for(i=0;rt;i=rt,rt=a[rt].f){
    			splay(rt);
    			a[rt].son[1]=i;
    			pushup(rt);
    		}
    		return i;
    	}
    	inline void split(int x){access(x);splay(x);}
    	inline void link(int x,int y){splay(x);a[x].f=y;}
    	inline void cut(int x){split(x);a[a[x].son[0]].f=0;a[x].son[0]=0;pushup(x);}
    }
    inline void add_que(int f,int x,int y,int id){
    	q++;
    	que[q].f=f;que[q].x=x;que[q].y=y;que[q].id=id;
    }
    void work(){
    	int f,x,y,lca;
    	for(int i=1,k=1;i<=n;i++)for(;que[k].id==i;k++){
    		f=que[k].f;x=que[k].x;y=que[k].y;
    		if(f>0){
    			LCT::split(x);ans[f]+=LCT::a[x].s;
    			lca=LCT::access_lca(y);LCT::splay(y);ans[f]+=LCT::a[y].s;
    			LCT::split(lca);ans[f]-=LCT::a[lca].s*2;
    		}
    		else{
    			LCT::cut(x);LCT::link(x,y);
    		}
    	}
    	for(int i=1;i<=m;i++)if(Ask[i])printf("%d
    ",ans[i]);
    }
    void init(){
    	int f,x,y,k,u,now;
    	n=read();m=read();
    	x=LCT::newnode(1);y=LCT::newnode(0);LCT::link(y,x);
    	now=2;
    	w[1]=s[1]=1;t[1]=n;
    	for(int i=1;i<=m;i++){
    		f=read();x=read();y=read();
    		if(f==0){
    			num++;
    			u=LCT::newnode(1);
    			w[num]=u;s[num]=x;t[num]=y;
    			add_que(i-m,u,now,1);
    		}
    		else if(f==1){
    			k=read();x=max(x,s[k]);y=min(y,t[k]);
    			if(x<=y){
    				u=LCT::newnode(0);
    				LCT::link(u,now);
    				add_que(i-m,u,w[k],x);
    				add_que(i-m,u,now,y+1);
    				now=u;
    			}
    		}
    		else{
    			k=read();
    			Ask[i]=true;
    			add_que(i,w[y],w[k],x);
    		}
    	}
    	sort(que+1,que+q+1);
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    
  • 相关阅读:
    平衡二叉树之RB树
    平衡二叉树之AVL树
    实现哈希表
    LeetCode Median of Two Sorted Arrays
    LeetCode Minimum Window Substring
    LeetCode Interleaving String
    LeetCode Regular Expression Matching
    PAT 1087 All Roads Lead to Rome
    PAT 1086 Tree Traversals Again
    LeetCode Longest Palindromic Substring
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9932672.html
Copyright © 2011-2022 走看看