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

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