1. 程式人生 > 其它 >Loj#2474-「2018 集訓隊互測 Day 3」北校門外的未來【LCT】

Loj#2474-「2018 集訓隊互測 Day 3」北校門外的未來【LCT】

正題

題目連結:https://loj.ac/p/2474


題目大意

開始有一個只有點\(1\)的圖,一個點\(x\)能走到點\(y\)當且僅當路徑\((x,y)\)之間(不包括\(x,y\))不存在編號比\(x\)\(y\)要大的節點。有\(m\)次操作:

  1. 新建一個編號為\(y\)的節點和\(x\)連線,保證編號不重複。
  2. 詢問\(x\)走到\(y\)最少需要走多少次。

節點編號在\(1\sim n\)之間

\(1\leq n\leq 10^5,1\leq m\leq 5\times 10^5\)


解題思路

神仙題。

考慮構造一個類似笛卡爾樹的東西,我們每次找到編號最大的點作為中心然後連線向它分割出來的連通塊的中心。(相當於找編號最大的點的點分樹)

我們記原樹為\(T\),這棵樹為\(T'\),那麼在\(T'\)上一個點能跳到的點肯定都是它的祖先。考慮怎麼樣的祖先能夠被他跳到,若\(x\)能跳到它的祖先\(y\),有兩種情況

  1. \(y\)\(x\)\(T'\)上的父節點
  2. \(T\)的路徑\((x,y)\)上距離\(y\)最近的點為\(z\),如果\(T'\)\(z\)\(x\)的子樹內,那麼\(x\)能跳到\(y\)。(這個不難證明,因為這樣\(x\sim y\)的路徑上沒有它的其他祖先)

並且還有一個性質,若\(x\)能跳到\(z\)\(y\)為他的父節點,那麼\(y\)也能跳到\(z\),因為顯然\(y\)繞去\(x\)

一圈回來都行。

那麼根據這個性質從貪心的角度思考,若我們的詢問為\(x,y\)距離較遠,視為\(x,y\)同時跳到它的\(LCA\),那在大部分時候我們\(x,y\)都往能到達的深度最淺的節點跳是優的。

事實上也是這樣,我們考慮\(x,y\)跳到\(x',y'\)滿足它們再往上跳深度就小於或等於\(LCA\)了,記此時的跳躍次數為\(c\),那麼答案肯定是\(c+2\)或者\(c+3\)
因為若\(x'\)\(y'\)都能跳到\(LCA\),次數就是\(c+2\),否則就都往上跳一步,這樣\(x'\)\(y'\)一點是祖孫關係(記\(x'\)深度小),又因為原來的\(x'\)能跳上來,那麼這個新的\(y'\)

也肯定能跳到那個位置,此時答案為\(c+3\)

好那麼現在我們就只需要處理祖孫的問題了,我們要支援對於\(x,y\)\(x\)跳到不超過\(y\)的最少步數和位置。這樣詢問時我們跳到\(x',y'\)然後再查詢\(x'\)\(y'\)能否到達\(LCA\)就好了。

\(G\)表示一棵樹,對於點\(x\)它的父節點就是它在\(T'\)上能過跳到的深度最小的節點。

那麼先考慮一次加點對\(T'\)的影響,如果\(x>y\),那麼\(y\)直接接在\(x\)的後面就好了,\(G\)上也是同理。如果\(x<y\),那麼則需要向上找到一個\(x\)的深度最淺的祖先\(z\)滿足\(z<y\)然後將\(y\)插在它的上面。
先考慮此時\(y\)能夠到達的點和原來\(z\)能夠到達的點是一樣的,直接在\(G\)上接替\(z\)的位置,\(z\)則先暫時接在\(y\)的後面。
然後需要注意的是此時\(x\)節點就是路徑\((z,y)\)上距離\(y\)最近的點,可以考慮利用這個性質來維護\(G\)。那此時能到達\(y\)節點的除了\(z\)以外,還有在\(T'\)上路徑\((x,y)\)的節點能直接到達\(y\)
並且這些節點在\(G\)上會接在\(y\)的後面的當且僅當他們原來接的點比\(y\)要大。

這樣說下來\(G\)似乎很難維護,因為既和\(T'\)的位置有關有和\(G\)的位置有關。考慮維護另一張圖\(G'\),在這張圖上我們將邊分為虛邊實邊,實邊是在\(G\)上實際存在的邊,虛邊則是代表這條邊連線的兩個節點在\(G\)上擁有同一個父親。

開始時我們儘量讓它保持\(T'\)的模樣,對於一個\((x,y,z)\)(與上文的意思相同),那麼我們提出路徑\((z,x)\)將其接到\(y\)的後面,然後整條路徑都變成虛邊,\(x\)\(y\)的連線則變成實邊。
會發現再到後面我們提路徑\((z,x)\)時會發現它在\(G'\)被分割成若干段,我們將這些段拼接起來。我們再把連上的邊在\(T'\)上標記起來,會發現這個過程是一個類似於\(LCT\)的Access的過程,均攤下來是\(O(n)\)次操作。

因為有斷邊連線操作,所以上述的\(G'\)我們需要用一個\(LCT\)來維護,同時還需要維護一個\(T'\)以方便我們找到每一段的位置。
然後我們需要精準的找到每一個實邊修改,我們可以維護一個實邊的和,然後在\(Splay\)上二分位置暴力修改。

時間複雜度:\(O(n\log^2 n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=1e5+10;
struct node{
	int to,next;
}a[N<<1];
int n,m,tot,op[N*5],rx[N*5],ry[N*5];
int cnt,pos[N],ls[N],fa[N],down[N],low[N];
int siz[N],dep[N],son[N],top[N],rfn[N],ed[N];
stack<int> stk;vector<int> G[N];
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void dfs1(int x){
	while(!stk.empty()&&pos[stk.top()]>pos[x])
		down[stk.top()]=x,stk.pop();
	//考慮若y>x那麼只有往y那部分跑的pos會小於pos[y] 
	stk.push(x);siz[x]=1;
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		fa[y]=x;dep[y]=dep[x]+1;
		dfs1(y);siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])son[x]=y;
	}
	return;
}
void dfs2(int x){
	rfn[x]=++cnt;
	if(son[x]){top[son[x]]=top[x];dfs2(son[x]);}
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		if(y==son[x])continue;
		top[y]=y;dfs2(y);
	}
	ed[x]=cnt;
	return;
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return (dep[x]<dep[y])?x:y;
}
int gtop(int x,int y){
	while(top[x]!=top[y]){
		if(fa[top[x]]==y)return top[x];
		x=fa[top[x]];
	}
	return son[y];
}
bool gfir(int x,int y){
	int z=gtop(x,y);
	return (rfn[x]<=rfn[low[z]]&&rfn[low[z]]<=ed[x]);
}
struct LCT{
	int t[N][2],fa[N],cfa[N],w[N],c[N],son[N];
	bool Nroot(int x)
	{return fa[x]&&(t[fa[x]][0]==x||t[fa[x]][1]==x);}
	bool Direct(int x)
	{return t[fa[x]][1]==x;}
	void PushUp(int x)
	{w[x]=w[t[x][0]]+w[t[x][1]]+c[x];return;}
	void Rotate(int x){
		int y=fa[x],z=fa[y];
		int xs=Direct(x),ys=Direct(y);
		int w=t[x][xs^1];
		if(Nroot(y))t[z][ys]=x;
		t[y][xs]=w;t[x][xs^1]=y;
		if(w)fa[w]=y;fa[y]=x;fa[x]=z;
		PushUp(y);PushUp(x);return;
	}
	void Splay(int x){
		while(Nroot(x)){
			int y=fa[x];
			if(!Nroot(y))Rotate(x);
			else if(Direct(x)==Direct(y))
				Rotate(y),Rotate(x);
			else Rotate(x),Rotate(x);
		}
		return;
	}
	void Access(int x){
		for(int y=0;x;y=x,x=fa[x])
			Splay(x),t[x][1]=y,PushUp(x);
		return;
	}
	int MakeTop(int x){
		Splay(x);
		if(!t[x][0])return fa[x];
		x=t[x][0];
		while(t[x][1])x=t[x][1];
		Splay(x);t[x][1]=0;PushUp(x);
		return x;
	}
	void Mdf(int x,int w)
	{Splay(x);c[x]=w;PushUp(x);return;}
	int GetTop(int x)
	{Splay(x);while(t[x][0])x=t[x][0];return x;}
	int GetBot(int x)
	{Splay(x);while(t[x][1])x=t[x][1];return x;}
	void Ins(int x,int y){
		if(x>y){fa[y]=cfa[y]=x;c[y]=w[y]=1;return;}
		int u,v;u=MakeTop(v=down[y]);
		Mdf(y,c[v]);Mdf(v,!u);
		fa[y]=u;fa[v]=y;
		if(son[u]==v)son[u]=y;
		if(u)son[y]=v;
		int z=0,now=0,low=x;
		while(x){
			Splay(x);t[x][1]=z;PushUp(x);
			if(w[x]){
				while(x){
					if(w[t[x][1]])x=t[x][1];
					else if(c[x])break;
					else x=t[x][0];
				}
				if(x>y)break;
				u=MakeTop(x);
				if(u>y)break;
				Splay(low);t[low][1]=0;
				if(son[low]){
					Splay(son[low]);
					fa[son[low]]=u;
					Mdf(son[low],1);
					son[low]=0;
				}
				int pre=cfa[x];Mdf(x,0);
				x=GetBot(x);Splay(x);
				if(now)fa[now]=x,son[x]=t[x][1]=GetTop(now);
				now=x;z=0;
				low=x=pre;
			}
			else z=x,x=fa[x];
		}
		cfa[y]=cfa[down[y]];cfa[down[y]]=y;
		if(now)now=GetTop(now),Mdf(now,1),fa[now]=y;
		return;
	}
	pair<int,int> Ask(int x,int y){
		if(x==y)return mp(0,0);
		Access(x);Splay(x);
		int dis=0,top=0,pre=x;
		while(x)
			if(x<y)top=x,x=t[x][0];
			else x=t[x][1];
		Splay(top);x=t[top][1];
		if(!w[x])return mp(0,pre);
		dis=w[x];
		while(x){
			if(w[t[x][0]])x=t[x][0];
			else if(c[x]){
				if(t[x][0]){
					x=t[x][0];
					while(t[x][1])x=t[x][1];
					return mp(dis,x);
				}
				return mp(dis,top);
			}
			else top=x,x=t[x][1];
		}
		return mp(dis,top);
	}
	int ct=0;
	int Query(int x,int y){
		ct++;
		if(ct==10)
			ct++,ct--;
		if(x==y)return 0;
		if(x>y)swap(x,y);
		int lca=LCA(x,y);
		if(lca==y){
			pair<int,int> A=Ask(x,y);
			int ans=A.first+2;
			if(gfir(A.second,y))ans--;
			return ans;
		}
		pair<int,int> A=Ask(x,lca);
		pair<int,int> B=Ask(y,lca);
		int ans=A.first+B.first+3;
		if(gfir(A.second,lca)&&gfir(B.second,lca))ans--;
		return ans;
	}
}T;
int find(int x)
{return (fa[x]==x)?(x):(fa[x]=find(fa[x]));}
int main()
{
	scanf("%d%d",&n,&m);n=1;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&op[i],&rx[i],&ry[i]);n=max(n,ry[i]);
		if(op[i]==1)addl(rx[i],ry[i]),addl(ry[i],rx[i]),pos[ry[i]]=i;
	}
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int x=1;x<=n;x++)
		for(int i=ls[x];i;i=a[i].next){
			int y=a[i].to;
			if(find(y)<x)
				G[x].push_back(find(y)),fa[find(y)]=x;
		}
	fa[n]=0;dep[n]=1;
	dfs1(n);top[n]=n;dfs2(n);
	for(int x=1;x<=n;x++)
		for(int i=ls[x];i;i=a[i].next){
			int y=a[i].to;
			if(x>y)low[gtop(y,x)]=y;
		}
	//low[x]表示點x子樹內離它父節點最近的點
	for(int i=1;i<=m;i++){
		if(op[i]==1)T.Ins(rx[i],ry[i]);
		else printf("%d\n",T.Query(rx[i],ry[i]));
	}
	return 0;
}