题意:
给出n个点的树,q个询问,每次询问给出3个数x,y,z问选两个点作为起点,到第三个点的路径有多少个点重合。
解题 思路:
distance(x,y)=distance(x,root)+distance(y, root)-2*distance(lca(x,y),root).
x,y为起点时,答案就是(dis(x,z)+dis(y,z)-dis(x,y))/2 。
重点讲一下倍增吧,第一次写,求LCA简直不能更简单。
倍增顾明思义就是成倍增大。两个点往上找lca的时候就是一个每次往上的距离成倍变大的一个过程。
倍增求LCA需要预处理祖先数组。fa[i][j],表示第i个点的第2^j个祖先,j从0开始。
这个处理起始非常简单,fa[i][j]=fa[fa[i][j-1][i].因为第2^j个祖先等价于当前点第2^(j-1)个祖先的第2^(j-1)个祖先,一次跑一遍就可以了。
另外还需要处理处每个点的深度,这个简单,必要的话要处理下到根节点的距离,没有边权的时候其实就是深度。
求a和b的lca的时候,先让深度大的节点先往上跳,跳的过程可以用二进制优化,设深度差为dif,把dif二进制拆分,只需要对为1的对应位j跳到第2^j个祖先就可以。
当深度一样时,判断下当前是否a和b是否相等,不相等让a和b同时往上跳,j从高到低判断下第2^j个祖先是否相同,相同就不往上跳,知道碰到祖先不同再往上跳。循环结束后,a和b的父亲就是lca了。
代码:
#include <bits/stdc++.h>
#define ps push_back
using namespace std;
const int maxn=1e5+5;
int dp[maxn][22];
int dep[maxn];
vector<int>edg[maxn];
void dfs(int x, int fa)
{
dep[x]=dep[fa]+1;
int v;
if(fa)
for(int i=1; i<20; i++)
{
dp[x][i]=dp[dp[x][i-1]][i-1];
}
for(int i=0; i<(int)edg[x].size(); i++)
{
v=edg[x][i];
if(v==fa)continue;
dp[v][0]=x;
dfs(v, x);
}
return;
}
int dis(int a, int b)
{
int x=a, y=b;
if(dep[a]<dep[b])
{
swap(a,b);
}
int i, dif=dep[a]-dep[b];
for(i=0; (1<<i)<=dif; i++)
{
if(dif&(1<<i))
{
a=dp[a][i];
}
}
if(a==b)
{
return dep[x]+dep[y]-2*dep[a];
}
for(i=20; i>=0 && a!=b; i--)
{
if(dp[a][i]==dp[b][i])continue;
a=dp[a][i]; b=dp[b][i];
}
a=dp[a][0];
return dep[x]+dep[y]-2*dep[a];
}
int query(int x, int y, int z)
{
// printf("%d %d %d\n", dis(x, z), dis(y, z), dis(x, y));
return (dis(x, z)+dis(y, z)-dis(x, y))/2;
}
int main()
{
int n, x, q, i, j;
cin>>n>>q;
for(i=2; i<=n; i++)
{
scanf("%d", &x);
edg[x].ps(i);
}
dp[1][0]=1;
dep[1]=1;
dfs(1, 0);
// for(i=1; i<=3; i++)printf("%d\n", dp[i][0]);
int y, z;
for(i=1; i<=q; i++)
{
scanf("%d%d%d", &x, &y, &z);
printf("%d\n", max(max(query(x, y, z), query(x, z, y)), query(z, y, x))+1);
}
}