原题大意
给定一个(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
",l,depth[r]);
else printf("%d %d
",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
",rdfn[mn],depth[L_lca]);
else printf("%d %d
",rdfn[mx],depth[R_lca]);
}
return 0;
}