• 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;
    }
    
  • 相关阅读:
    Typescript类、命名空间、模块
    TypeScript 基础类型、变量声明、函数、联合类型、接口
    JS中的单线程与多线程、事件循环与消息队列、宏任务与微任务
    wangEditor上传本地视频
    java版excel转pdf,word转pdf
    idea2019.3 没有 Autoscroll from Source
    mysql 实现类似oracle函数bitand功能
    spring boot 配置文件动态更新原理 以Nacos为例
    spring boot 发布自动生成svn版本号
    spring boot JPA 数据库连接池释放
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9932672.html
Copyright © 2020-2023  润新知