1. 程式人生 > >●洛谷P3348 [ZJOI2016]大森林

●洛谷P3348 [ZJOI2016]大森林

ems href define r+ pan bits found www AD

題鏈:

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;
}

  

●洛谷P3348 [ZJOI2016]大森林