zoukankan      html  css  js  c++  java
  • ●洛谷P3348 [ZJOI2016]大森林

    题链:

    https://www.luogu.org/problemnew/show/P3348

    题解:

    LCT,神题
    首先有这么一个结论:
    每次的1操作(改变生长点操作),一定只会会对连续的一段区间产生影响。
    (即不存在对两段不相连的区间都进行了该操作的情况,令这种情况为[2])
    简单来说就是因为该操作需要对应的那些树存在那个节点。
    如果发生了情况[2],即表明区间[l1,r1],[l2,r2]有节点x,且[l1+1,r2-1]无节点x,且r2-1>=l1+1
    但是我们的生长操作0操作每次进行的区间都是连续的,且每次产生的节点的编号都不同,
    所以不会存在"区间[l1,r1],[l2,r2]有节点x,且[l1+1,r2-1]无节点x,且r2-1>=l1+1"这种情况
    也就不会有情况[2]了。

    所以,我们可以记录下来每种节点x的在哪段区间里L[x],R[x]。
    然后对于一个1操作(1,l,r,x),我们就可以得到其真正会影响的那段连续区间[max(l,L[x]),min(r,R[x])]
    再强调一下,对于计算出来的区间[max(l,L[x]),min(r,R[x])],该操作是一定会影响到的,同时也只会影响到该区间。

    然后再看看由于询问保证一定给出的点存在,
    所以既然我们都已经得到了1操作会真正影响的区间,那么对于所有的0操作(生长操作),
    我们就可以忽略掉其给出的区间限制,而让整个[1,N]的区间都执行这个操作。
    这样并不会影响答案。
    (因为询问不会问到这个点,1操作也因为已经算出了其确切会影响的区间)。

    由于树很多,不能同时维护这么多颗树,我们考虑离线,并用LCT维护
    把所有操作和询问按其影响的区间的端点挂在对应的位置上,
    依次维护每一颗树,并回答关于该树的问题。
    同样由于询问保证一定给出的点存在,所以我们把询问放在最后来做。(即把当前树的形态维护好后,再回答所有询问)
    然后是如何维护树的形态,
    1.对于一个每一个1操作(1,l,r,x),我们都新建一个虚节点,权值为0,
    初始时该点先连在上一个虚节点上,之后所有的点都连在这个新的虚节点上面。
    然后当到了第[max(l,L[x])颗树时,就让它连着它下面那一包东西,
    先和它的父亲断开,再整体连到x对应的节点上去,实现了把之后的节点长在x上操作。
    然后当到了第min(r,R[x])]+1颗树时,就让该虚点又连着它下面的那一包东西,回到之前的位置上去,实现了该操作没有影响的区间,生长节点不动这个操作。
    2.对于一个0操作,因为我们让它对全区间生效,
    所以只要遇到0操作,就把该操作对应的权值为1的节点link到上一个虚节点,以便之后随着虚节点被一起打包着走。
    3.对于2操作,现在需要询问了,由于该树的形态已经维护好,所以直接找到两个点lca并回答即可。

    (思路太妙啊,我都不知道下次遇到类似的题目能不能想出这种解法。2333)


    代码:

    #include<bits/stdc++.h>
    #define MAXN 400005
    using namespace std;
    struct LCT{
    	int lnt;
    	int ch[MAXN][2],size[MAXN],val[MAXN],fa[MAXN];
    	LCT(){lnt=1;}
    	bool Who(int x){return ch[fa[x]][0]!=x;}
    	bool Isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    	int Newnode(int v){
    		fa[lnt]=ch[lnt][0]=ch[lnt][1]=0;
    		size[lnt]=val[lnt]=v;
    		return lnt++;
    	}
    	void Pushup(int x){
    		size[x]=val[x];
    		if(ch[x][0]) size[x]+=size[ch[x][0]];
    		if(ch[x][1]) size[x]+=size[ch[x][1]];
    	}
    	void Rotate(int x){
    		static int y,z,l1,l2;
    		y=fa[x]; z=fa[y]; 
    		l1=Who(x); l2=Who(y); fa[x]=z;
    		if(!Isroot(y)) ch[z][l2]=x;
    		fa[ch[x][l1^1]]=y; fa[y]=x;
    		ch[y][l1]=ch[x][l1^1]; ch[x][l1^1]=y;
    		Pushup(y);
    	}
    	void Splay(int x){
    		static int y;
    		for(;y=fa[x],!Isroot(x);Rotate(x))
    			if(!Isroot(y)) Rotate(Who(y)==Who(x)?y:x);
    		Pushup(x);
    	}
    	int Access(int x){
    		static int y;
    		for(y=0;x;y=x,x=fa[x])
    			Splay(x),ch[x][1]=y,Pushup(x);
    		return y;
    	}
    	int Findlca(int x,int y){
    		Access(x); return Access(y);
    	}
    	void Link(int x,int y){
    		//Splay(y); It's not neccessary to do the Splay operation.
    		fa[x]=y;
    	}
    	void Cut(int x){
    		Access(x); Splay(x);
    		fa[ch[x][0]]=0; ch[x][0]=0; Pushup(x);
    	}
    }DT;
    struct Cmd{
    	int pos,odr,from,to;
    	Cmd(){}
    	Cmd(int _a,int _b,int _c,int _d):pos(_a),odr(_b),from(_c),to(_d){}
    	bool operator < (const Cmd &rtm) const{
    		return pos<rtm.pos||(pos==rtm.pos&&odr<rtm.odr);
    	}
    }Q[MAXN];
    int id[MAXN],L[MAXN],R[MAXN],ANS[MAXN];
    int N,M,unreal,dnt=1,cnt=1;
    int main(){
    	ios::sync_with_stdio(0);
    	cin>>N>>M;
    	L[dnt]=1; R[dnt]=N;
    	id[dnt++]=DT.Newnode(1);
    	unreal=DT.Newnode(0);
    	DT.Link(unreal,id[1]);
    	for(int i=1,t,l,r,x,tmp;i<=M;i++){
    		cin>>t;
    		switch(t){
    			case 0:
    				cin>>l>>r;
    				L[dnt]=l; R[dnt]=r;
    				id[dnt]=DT.Newnode(1);
    				 
    				Q[cnt++]=Cmd(1,i-M,id[dnt],unreal);
    				dnt++;
    				//Can the real node be linked to unreal node at now instead of doing this later?
    				//DT.Link(id[dnt],unreal);
    				//After the try,I found it okay to do this.
    				break;
    			case 1:
    				cin>>l>>r>>x;
    				l=max(l,L[x]); r=min(r,R[x]);
    				if(l<=r){
    					tmp=DT.Newnode(0);
    					DT.Link(tmp,unreal);
    					Q[cnt++]=Cmd(l,i-M,tmp,id[x]);
    					Q[cnt++]=Cmd(r+1,i-M,tmp,unreal);
    					unreal=tmp;
    				}
    				break;
    			case 2:
    				cin>>x>>l>>r;
    				Q[cnt++]=Cmd(x,i,id[l],id[r]);
    				break;
    		}
    	}
    	sort(Q+1,Q+cnt);
    	memset(ANS,-1,sizeof(ANS));
    	for(int tree=1,i=1,x,y;i<cnt&&tree<=N;tree++){
    		while(Q[i].pos==tree){
    			x=Q[i].from; y=Q[i].to;
    			if(Q[i].odr>0){
    				int l1,l2,l3,lca;
    				DT.Access(x); DT.Splay(x); l1=DT.size[x];
    				lca=DT.Findlca(x,y); DT.Access(lca); DT.Splay(lca); l2=DT.size[lca];
    				DT.Access(y); DT.Splay(y); l3=DT.size[y];
    				ANS[Q[i].odr]=l1+l3-2*l2;
    			}
    			else DT.Cut(x),DT.Link(x,y);
    			i++;
    		}
    	}
    	for(int i=1;i<=M;i++) if(ANS[i]>=0) cout<<ANS[i]<<endl;
    	return 0;
    }
    

      

  • 相关阅读:
    配置cinder使用NFS后端
    配置glance使用NFS后端
    制作windows镜像
    fuel健康检查Heat失败的原因
    重启OpenStack服务步骤
    改变nova-compute默认位置的方法
    改变cinder默认vg的方法
    centos lvm常用命令
    【一天一个canvas】图像处理教程(十二)
    【一天一个canvas】阴影效果呈现方法(十一)
  • 原文地址:https://www.cnblogs.com/zj75211/p/8541613.html
Copyright © 2011-2022 走看看