CF1062E 线段树/LCA

原题大意

给定一个\(n\)个点的以\(1\)为根的子树,定义根的深度为\(0\).给出\(q\)个询问,每个询问给定一个区间\([l,r]\)输出在此区间内任意删除一个点之后,剩下的所有的点的\(lca\)的深度最大值.

数据范围:

\(1 \leq n,q \leq 10^5\)

思路

看到多点求\(lca\)有个很经典的结论:多个点的lca是这些点中dfn最小的点和最大的点的lca.虽然现在还不知道它有什么用,先放着.

性质

  • 任意多个点的lca等于dfn最小的点和最大的点的lca
  • 记最小的点是\(a\),最大的点是\(b\).首先可以证明,这两个点的\(lca\)一定是集合内所有点的祖先,假设某个点不是这个点的子孙节点,如果这个点的dfn值在\(a,b\)之间,那么从根节点往下遍历的时候势必会遍历到这个点,那么这个点一定是夹在中间的,虽然求的lca不一定是根节点,但是一定存在一条根节点从上往下走到这个lca并走到这个点的路径,否则和dfn的定义矛盾了.也就是说除非这个点的dfn是超过这两个点范围的,否则必然存在一个路径到达他,也就必然是他的祖先节点.
  • 其次可以证明不存在比这个点的更靠下的点,也就是他是最近的点,可以假设存在一个点比他的深度更大,那就是更近,那么显然和他是\(a,b\)的lca这个定义矛盾了,所以这个点也一定是最近的.

那么这个性质有个很奇特的地方:他可以说明:如果在一段区间里删除一个数,那么如果没有删最左侧最右侧(dfn最小最大的点)的话,是不会对这段区间的点的lca产生影响的,那么接下来可以简单讨论一下:

  • 整个区间只有两个数,此时直接判断选谁的深度比较大就可以了
  • 区间的长度是\(3\)以上,此时可以删除中间的某个点,则lca保持不变,取深度即可.
  • 删除最小值,那么现在的最小值会由原来的次小值的点替换上,对次小值和最大值求lca即可.
  • 删除最大值,那么现在的最大值会由原来的次大值的点替换上,对次大值和最小值求lca即可.

那么有关查询最大值最小值,套一个RMQ即可.lca可以用树上倍增或者其他方法求.我用的是线段树+树上倍增,查询一次的复杂度是\(O(log^2n)\).这个题的数据范围还是比较小的,可以直接过掉.当然也可以极限一点,使用同样预处理是\(O(nlogn)\)的ST表以及树链剖分求lca,就可以把单次查询降到\(O(1)\)了.

在具体实现的时候,线段树维护最大值最小值,只维护最大值最小值时,不知道对应的点的关系,所以有一个反映射的\(rdfn\)数组,因为\(dfn\)本身的取值就是唯一的所以没什么影响,当然更好的做法是在维护线段树的使用使用点而不是值.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7,M = 2 * N,LIM = 18,INF = 1e9;
struct Node
{
	int l,r;
	int mx,mn;
}tr[N * 4];

int edge[M],succ[M],ver[N],idx;
int depth[N],fa[N][LIM + 3];
int dfn[N],time_stamp;
int rdfn[N];

void add(int u,int v)
{
	edge[idx] = v;
	succ[idx] = ver[u];
	ver[u] = idx++;
}

void dfs(int u,int father)
{
	dfn[u] = ++time_stamp;rdfn[time_stamp] = u;
	for(int i = ver[u];~i;i = succ[i])
	{
		int v = edge[i];
		if(v == father)	continue;
		if(depth[v] > depth[u] + 1)
		{
			depth[v] = depth[u] + 1;
			fa[v][0] = u;
			for(int k = 1;k <= LIM;++k)
				fa[v][k] = fa[fa[v][k - 1]][k - 1];
			dfs(v,u);
		}
	}
}

int lca(int x,int y)
{
	if(depth[x] <= depth[y])	swap(x,y);
	for(int k = LIM;k >= 0;--k)
		if(depth[fa[x][k]] >= depth[y])
			x = fa[x][k];
	if(x == y)	return x;
	for(int k = LIM;k >= 0;--k)
		if(fa[x][k] != fa[y][k])
			x = fa[x][k],y = fa[y][k];
	return fa[x][0];
}

void pushup(int u)
{
	auto& s = tr[u],&lf = tr[u << 1],&rt = tr[u << 1 | 1];
	s.mx = max(lf.mx,rt.mx);
	s.mn = min(lf.mn,rt.mn);
}

void build(int u,int l,int r)
{
	if(l == r)	tr[u] = {l,r,dfn[l],dfn[r]};
	else
	{
		int mid = l + r >> 1;
		tr[u] = {l,r,-INF,INF};
		build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
		pushup(u);
	}
}

int query_max(int u,int l,int r)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].mx;
	int mid = tr[u].l + tr[u].r >> 1,res = 0;
	if(l <= mid)	res = max(res,query_max(u << 1,l,r));
	if(r > mid)		res = max(res,query_max(u << 1 | 1,l,r));
	return res;
}

int query_min(int u,int l,int r)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].mn;
	int mid = tr[u].l + tr[u].r >> 1,res = INF;
	if(l <= mid)	res = min(res,query_min(u << 1,l,r));
	if(r > mid)		res = min(res,query_min(u << 1 | 1,l,r));
	return res;
}
int main()
{
	memset(ver,-1,sizeof ver);
	int n,q;scanf("%d%d",&n,&q);
	forn(i,2,n)
	{
		int p;scanf("%d",&p);
		add(i,p),add(p,i);
	}
	
	memset(depth,0x3f,sizeof depth);
	depth[0] = -1,depth[1] = 0;
	dfs(1,-1);
	
	build(1,1,n);

	while(q--)
	{
		int l,r;scanf("%d%d",&l,&r);
		if(r - l + 1 == 2)
		{
			if(depth[l] < depth[r])	printf("%d %d\n",l,depth[r]);
			else printf("%d %d\n",r,depth[l]);
			continue;
		}
		int mx = query_max(1,l,r),mn = query_min(1,l,r);
		int smx = max(query_max(1,l,rdfn[mx] - 1),query_max(1,rdfn[mx] + 1,r));
		int smn = min(query_min(1,l,rdfn[mn] - 1),query_min(1,rdfn[mn] + 1,r));
		int L_lca = lca(rdfn[smn],rdfn[mx]),R_lca = lca(rdfn[mn],rdfn[smx]);
		if(depth[L_lca] > depth[R_lca])	printf("%d %d\n",rdfn[mn],depth[L_lca]);
		else printf("%d %d\n",rdfn[mx],depth[R_lca]);
	}
	
    return 0;
}
posted @ 2021-01-27 09:51  随处可见的阿宅  阅读(138)  评论(0编辑  收藏  举报