1. 程式人生 > 其它 >【CF1137F】Matches Are Not a Child's Play(LCT)

【CF1137F】Matches Are Not a Child's Play(LCT)

給定一棵 $n$ 個點的無根樹,定義其刪除序列:每次將樹中編號最小的葉節點刪除並加入序列最末端。共 $q$ 次操作,分為三種:將某個節點的編號設為其餘節點編號的最大值$+1$;詢問某個節點在刪除序列中的位置;詢問兩個節點在刪除序列中的先後。

題目連結

  • 給定一棵 \(n\) 個點的無根樹,定義其刪除序列:每次將樹中編號最小的葉節點刪除並加入序列最末端。
  • \(q\) 次操作,分為三種:將某個節點的編號設為其餘節點編號的最大值\(+1\);詢問某個節點在刪除序列中的位置;詢問兩個節點在刪除序列中的先後。
  • \(1\le n,q\le2\times10^5\)

刪除序列的性質

顯然,最大點一定在刪除序列中的最末端。因為除非只剩最後一個點,葉節點一定不止一個,也就肯定存在葉節點比最大點編號更小。

然後考慮次大點,找出它與最大點之間的樹上路徑,則這條路徑上的點一定從次大點開始依次出現在刪除序列的最末端。同樣因為除非葉節點恰好為次大點和最大點,肯定存在葉節點比次大點編號更小;而刪去次大點之後,剩下這條路徑上所有點就會被依次刪去。

以此類推,從大到小考慮每個點,如果它尚未確定,則它到之前已確定部分的路徑在刪除序列中會恰好出現在之前已確定部分的前面、尚未確定部分的最末端。

這種東西似乎難以實現。但我們反過來考慮,從小到大考慮每個點,將它到最大點的路徑打通(打通指的是隻保留這條路徑上的邊,而隱去這條路徑上的點其他的連邊)。因為較大點的路徑會更後打通,所以最終以某個點為底端的連通鏈就是這個點到它之前已確定部分的路徑。

而這個所謂的“打通”就是以最大點為根時 LCT 中的 Access 操作。

所以我們只要維護好這些連通鏈,將它們按底端編號排序,則一個點在刪除序列中的排名就是它所在鏈之前所有連通鏈大小之和加上它在所在鏈中的排名。

這可以用一個以底端編號為下標的樹狀陣列來維護。

處理修改操作

考慮修改操作,被操作的點一定會變成最大點。

找出它與原最大點之間的路徑,根據先前的結論它們將在最後被刪除,應當形成一條新的連通鏈。

而其餘的連通鏈在除去現最大點與原最大點之間的部分後並不會發生變化。

所以實際上只需要把根改為現最大點,然後打通原最大點到現最大點的路徑即可。

程式碼:\(O(n\log n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	I void readc(char& x) {W(isspace(x=tc()));}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int Tsz;struct TreeArray//樹狀陣列
{
	int a[2*N+5];I void A(RI x,CI v) {if(!~x) return;W(x<=Tsz) a[x]+=v,x+=x&-x;}
	I int Q(RI x) {RI t=0;W(x) t+=a[x],x-=x&-x;return t;}
}T;
class LinkCutTree
{
	private:
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1)
		#define PD(x) (O[x].P&&(Ts(O[x].S[0],O[x].P),Ts(O[x].S[1],O[x].P),O[x].P=0),O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0))
		#define Ts(x,v) (O[x].G=O[x].P=v)
		#define Re(x) (x)&&(swap(O[x].S[0],O[x].S[1]),O[x].R^=1)
		int St[N+5];struct node {int Sz,G,P,F,R,S[2];}O[N+5];
		I void Ro(RI x) {RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f);}
		I void S(RI x) {RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);PU(x);}
		I int FR(RI x) {Ac(x),S(x);W(O[x].S[0]) PD(x),x=O[x].S[0];return S(x),x;}
	public:
		I void Init() {O[0].G=-1;for(RI i=1;i<=n;++i) O[i].G=-1,O[i].Sz=1;}//初始化
		I void Link(CI x,CI y) {MR(x),O[x].F=y;}I void MR(CI x) {Ac(x),S(x),Re(x);}
		I int Q(CI x) {return S(x),T.Q(O[x].G-1)+O[O[x].S[1]].Sz+1;}//之前所有連通鏈大小之和+在所在鏈中的排名
		I void Ac(RI x,CI tg=-1)//打通,標記為tg
		{
			RI y;for(y=0;x;x=O[y=x].F) S(x),T.A(O[x].G,-O[x].Sz),T.A(O[O[x].S[1]].G,O[O[x].S[1]].Sz),O[x].S[1]=y,PU(x);//打通,與右兒子斷開
			Ts(y,tg),T.A(tg,O[y].Sz);//將新的連通鏈標記為tg
		}
}LCT;
int main()
{
	RI Qt,i,x,y;for(read(n,Qt),Tsz=n+Qt,LCT.Init(),i=1;i^n;++i) read(x,y),LCT.Link(x,y);
	for(LCT.MR(n),i=1;i^n;++i) LCT.Ac(i,i);//以最大點為根,從小到大將每個點到最大點路徑打通
	char op;RI rt=n,tt=n;W(Qt--)
	{
		if(readc(op),read(x),op=='u') {rt^x&&(LCT.MR(x),LCT.Ac(rt,tt),rt=x,++tt);continue;}//修改
		op=='w'?writeln(LCT.Q(x)):(read(y),writeln(LCT.Q(x)<LCT.Q(y)?x:y));//詢問,判斷先後直接詢問位置比大小
	}return clear(),0;
}
敗得義無反顧,弱得一無是處