jzoj3691. 【CF414E】Mashmokh's Designed tree/codeforces414E

題目描述

Description
在大量的試驗之後,Mashmokh 設計了一個問題,然後,你的職責是解決它。

你得到一棵有n 個點的樹T。每個點有一個獨一無二的1 到n 之內的編號。樹T 根的編號爲1。對於樹中每個點v,你會得到它兒子們按照特定順序而給出的列表。你須處理在這棵樹上的三種詢問:

  1. 計算u 到v 的距離(最短路的邊數);

  2. 給出v 和h,斷開v 和他父親的邊,然後將它和它第h個祖先相連;更

正式的說法是,讓我們記從v 到根的路徑爲x1, x2,… ; x_L(h <L),因此x1 = v 且x_L 爲根;將v 與父親(x2)的邊斷開,然後將它連上x_h+1;點v 必須添加到點x_h+1 的兒子列表的末尾;

  1. 在調用函數dfs(root) 產生的點序列裏,找到其中最後的與根的距離爲

k 的點。

函數dfs(v) 的僞代碼如下:
// ls[v]: list of children of vertex v
// its i-th element is ls[v][i]
// its size is size(ls[v])
sequence result = empty sequence;
void dfs(vertex now)
{
add now to end of result;
for(int i = 1; i <= size(ls[v]); i = i + 1) //loop from i = 1 to i = size(ls[v])
dfs(ls[v][i]);
}
Input
輸入的第一行包含兩個用空格隔開的整數n,m(2 <= n <= 10^5; 1 <= m <= 10^5),T 的點數及需要處理的詢問數。

接下來n 行的第i 行包含一個整數li(0 <= li <= n),第i 個點兒子的數目。

然後緊跟着的是li 個用空格隔開的整數,他們的第j 個爲點i 的第j 個兒子的

編號。注意,這些點之間的順序很重要。

接下來m行每行滿足以下格式中的一個: “1 v u”,“2 v h”,或“3 k”。行中的第一個數爲題目描述中需要處理的詢問的類型。後面緊跟着的數字爲詢問的

參數。

保證所有詢問都是正確的。例如,第二類詢問中h 至少爲2,最多爲v 到根的距離。同樣,在第三類詢問給出時,有至少一個點與根的距離爲k。

Output
對於每個第一類或第三類詢問輸出一行,包含詢問的結果。

Sample Input
輸入1:

4 9

1 2

1 3

1 4

0

1 1 4

2 4 2

1 3 4

3 1

3 2

2 3 2

1 1 2

3 1

3 2

輸入2:

2 2

1 2

0

1 2 1

3 1

Sample Output
輸出1:

3

2

2

4

1

3

4

輸出2:

1

2

Data Constraint
對於30% 的數據,有n,m <= 1000。

題解

碼農題(2/3)

貌似可以LCT+ETT維護?反正只用ETT也可以
其實也短不了多少


ETT全名Euler Tour Tree,即用平衡樹維護歐拉序
爲了維護深度,每個點上同時維護括號序的前綴和(即進棧+1,出棧-1)
在這裏插入圖片描述
(歐拉序維護的是點的順序,所以把上圖的根設爲0)
對於歐拉序
01266244133550
括號序的和爲
12343232121210
同時對於每個點,在ETT中的兩個點(稱之爲左右括號)都維護在原樹上的父親


ETT能完成很多操作,以題目爲例
①求x和y之間的最短路
其實就是deep[x]+deep[y]-2deep[lca]
問題變成了求lca的深度
可以發現,deep[lca]=[x的左括號~y的左括號]中最小的深度
分兩種情況來看
1、如果x和y之間有祖先關係,那麼找到的是x或y的左括號
2、如果x和y之間沒有祖先關係,那麼在走完一顆樹時會退到lca的深度,即走到一個屬於lca兒子的右括號(此時可以用記錄的原樹父親來求出lca,但是本題沒有必要)

②斷開x,把x接爲h級祖先的最後一個兒子
先找到[1~x的左括號]中最後一個深度爲deep[x]-h的點
(由於歐拉序中深度是連續的一段區間,所以可以維護區間內深度最小/最大值來找)
同樣分兩種情況
1、找到的是一個左括號,那麼就是x的h級祖先
2、找到的是一個右括號,那麼x的h級祖先一定是這個點的原樹父親
(一個點在退棧時的深度=父親的深度,該點深度=父親深度+1,並且由1可得找到的點不會在h級祖先的子樹外,所以找到的是h級祖先的一個兒子)
然後基本操作區間移動

③找到深度爲k的最後一個點
類似②,找[1~n]中最後一個深度爲k的點
分類
1、左括號,就是這個點
2、右括號,類似②,找到的實際上是最後一個深度爲k+1的點,那麼這個點的父親就是最後一個深度爲k的點

code

極其清真

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;

int a[100001][2];
int ls[100001];
int son[100001];
int bg[100001];
int ed[100001];
int tr[200003][7]; //0ls 1rs 2deep 3mnd 4mxd 5tag 6fa in real tree
int fa[200003];
int b[200003];
int N,n,Q,i,j,k,l,len,type,x,y,s;

void New(int x,int y)
{
	++len;
	a[len][0]=y;
	a[len][1]=ls[x];
	ls[x]=len;
}

void dfs(int Fa,int t,int d)
{
	int i;
	
	bg[t]=++j;
	b[j]=t;
	tr[j][2]=d;
	tr[j][3]=d;
	tr[j][4]=d;
	tr[j][6]=Fa;
	
	for (i=ls[t]; i; i=a[i][1])
	dfs(t,a[i][0],d+1);
	
	ed[t]=++j;
	b[j]=t;
	tr[j][2]=d-1;
	tr[j][3]=d-1;
	tr[j][4]=d-1;
	tr[j][6]=Fa;
}

void down(int t)
{
	if (tr[t][5])
	{
		if (tr[t][0])
		tr[tr[t][0]][5]+=tr[t][5];
		if (tr[t][1])
		tr[tr[t][1]][5]+=tr[t][5];
		
		tr[t][2]+=tr[t][5];
		tr[t][3]+=tr[t][5];
		tr[t][4]+=tr[t][5];
		tr[t][5]=0;
	}
}

void up(int t)
{
	tr[t][3]=tr[t][2];
	tr[t][4]=tr[t][2];
	
	if (tr[t][0])
	{
		tr[t][3]=min(tr[t][3],tr[tr[t][0]][3]+tr[tr[t][0]][5]);
		tr[t][4]=max(tr[t][4],tr[tr[t][0]][4]+tr[tr[t][0]][5]);
	}
	if (tr[t][1])
	{
		tr[t][3]=min(tr[t][3],tr[tr[t][1]][3]+tr[tr[t][1]][5]);
		tr[t][4]=max(tr[t][4],tr[tr[t][1]][4]+tr[tr[t][1]][5]);
	}
}

void rot(int t)
{
	int Fa=fa[t],Fa2=fa[Fa];
	int x=tr[Fa][1]==t,x2=tr[Fa2][1]==Fa;
	int son=tr[t][x^1];
	
	down(Fa);
	down(t);
	
	fa[t]=Fa2;
	tr[Fa2][x2]=t;
	
	fa[Fa]=t;
	tr[t][x^1]=Fa;
	
	fa[son]=Fa;
	tr[Fa][x]=son;
	
	up(Fa);
	up(t);
}

void splay(int x,int t)
{
	int Fa,Fa2;
	
	while (fa[t]!=x)
	{
		Fa=fa[t];
		
		if (fa[Fa]!=x)
		{
			Fa2=fa[Fa];
			
			if (!((tr[Fa2][0]==Fa)^(tr[Fa][0]==t)))
			rot(Fa),rot(t);
			else
			rot(t),rot(t);
		}
		else
		rot(t);
	}
}

void mt(int Fa,int l,int r)
{
	int mid=(l+r)/2;
	
	fa[mid]=Fa;
	
	if (l==r)
	return;
	
	if (l+1==r)
	{
		tr[l][1]=r;
		mt(l,r,r);
	}
	else
	{
		tr[mid][0]=(l+mid-1)/2;
		mt(mid,l,mid-1);
		
		tr[mid][1]=(mid+1+r)/2;
		mt(mid,mid+1,r);
	}
	
	up(mid);
}

int main()
{
	scanf("%d%d",&n,&Q);
	N=n+n+2;
	fo(i,1,n)
	{
		scanf("%d",&l);
		fd(j,l,1)
		scanf("%d",&son[j]);
		
		fo(j,1,l)
		New(i,son[j]);
	}
	
	j=1;
	dfs(0,1,1);
	++j;
	bg[0]=1;
	ed[0]=N;
	
	mt(0,1,N);
	
	for (;Q;--Q)
	{
		scanf("%d",&type);
		
		switch (type)
		{
			case 1:{
				scanf("%d%d",&x,&y);
				
				if (x==y)
				{
					printf("0\n");
					continue;
				}
				
				splay(0,bg[x]);
				splay(bg[x],bg[y]);
				down(bg[x]);
				down(bg[y]);
				
				s=min(tr[bg[x]][2],tr[bg[y]][2]);
				
				if (tr[bg[x]][0]==bg[y])
				{
					if (tr[bg[y]][1])
					s=min(s,tr[tr[bg[y]][1]][3]+tr[tr[bg[y]][1]][5]);
				}
				else
				{
					if (tr[bg[y]][0])
					s=min(s,tr[tr[bg[y]][0]][3]+tr[tr[bg[y]][0]][5]);
				}
				
				printf("%d\n",tr[bg[x]][2]+tr[bg[y]][2]-s-s);
				break;
			}
			
			case 2:{
				scanf("%d%d",&x,&y);
				splay(0,bg[x]);
				down(bg[x]);
				
				i=tr[bg[x]][0];
				while (1)
				{
					down(i);
					
					if (tr[i][1] && tr[tr[i][1]][3]+tr[tr[i][1]][5]<=tr[bg[x]][2]-y && tr[bg[x]][2]-y<=tr[tr[i][1]][4]+tr[tr[i][1]][5])
					i=tr[i][1];
					else
					{
						if (tr[i][2]==tr[bg[x]][2]-y)
						break;
						else
						i=tr[i][0];
					}
				}
				if (ed[b[i]]==i)
				i=tr[i][6];
				else
				i=b[i];
				
				splay(bg[x],ed[x]);
				
				j=tr[ed[x]][1];
				while (tr[j][0])
				j=tr[j][0];
				splay(ed[x],j);rot(j);rot(j);
				
				k=tr[bg[x]][0];
				while (tr[k][1])
				k=tr[k][1];
				splay(bg[x],k);rot(k);
				
//				---
				
				down(j);
				down(k);
				
				tr[k][1]=0;
				fa[bg[x]]=0;
				
				up(k);
				up(j);
				
//				---
				
				splay(0,ed[i]);
				
				j=tr[ed[i]][0];
				while (tr[j][1])
				j=tr[j][1];
				splay(0,j);
				
				tr[bg[x]][5]-=y-1;
				down(bg[x]);
				tr[ed[i]][0]=bg[x];
				fa[bg[x]]=ed[i];
				
				tr[bg[x]][6]=i;
				tr[ed[x]][6]=i;
				
				up(ed[i]);
				up(j);
				
				break;
			}
			
			case 3:{
				scanf("%d",&x);
				++x;
				
				i=1;
				while (fa[i])
				i=fa[i];
				
				while (1)
				{
					down(i);
					
					if (tr[i][1] && tr[tr[i][1]][3]+tr[tr[i][1]][5]<=x && x<=tr[tr[i][1]][4]+tr[tr[i][1]][5])
					i=tr[i][1];
					else
					{
						if (tr[i][2]==x)
						break;
						else
						i=tr[i][0];
					}
				}
				
				if (ed[b[i]]==i)
				printf("%d\n",tr[i][6]);
				else
				printf("%d\n",b[i]);
				
				break;
			}
		}
	}
}

參考資料

https://blog.csdn.net/jacajava/article/details/84475246
https://blog.csdn.net/zlttttt/article/details/78747431
https://blog.csdn.net/icefox_zhx/article/details/80691076
https://www.cnblogs.com/jefflyy/p/8352751.html
https://en.wikipedia.org/wiki/Euler_tour_technique

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章