题意:给定一颗树(n<=1e5),并有q(<=1e5)次询问,每次询问指定三个点a,b,c:意思是有两个人任选两个点作为各自起点,剩下的那个点作为终点,两个人各自从起点走向终点,求路径上重合的点数,现在要求在这三个点可能的选择方式中,求重合点数的最大值。
题解:对于三个不同的点a,b,c,画出两两间的道路,他们必然呈现出三岔路的形状。科学点说就是:a,b,c三点里,必有一对点的lca在某两点的路径上,而三岔路口的地方就是所说的lca,这个画个图可以很容易的讨论证明。所以只需要求出三个lca,并取深度最大的那个,就是我们要的三岔路口K,然后分别求出K到a,b,c三点的路径长度,取最大值+1就是答案。
而当有两个点相同,第三个点不同的情况,可以得知:答案=唯一的路径长+1,和我们算法给出的答案是一致的。
当三个点都相同,答案也是一致的。
所以不需要讨论。
Code:
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
int deep[MAXN];
vector<int> E[MAXN];
int father[MAXN][20];
int n,q;
void build (int nod,int fa,int depth){
deep[nod] = depth;
for (vector<int>::iterator it = E[nod].begin();it!=E[nod].end();it++){
if ((*it)!=fa){
father[*it][0] = nod;
build (*it,nod,depth+1);
}
}
}
void initLCA(){
for (int i=1;i<=19;i++){
for (int j = 1;j<=n;j++){
father[j][i] = father[father[j][i-1]][i-1];
}
}
}
int lca(int a,int b){
if (deep[a]<deep[b]){
swap(a,b);
}
for (int i=19;i>=0;i--){
if (deep[father[a][i]]>=deep[b]){
a = father[a][i];
}
}
if (a==b){
return a;
}
for (int i=19;i>=0;i--){
if (father[a][i]!=father[b][i]){
a = father[a][i];
b = father[b][i];
}
}
return father[a][0];
}
int length (int a,int b){
int anc = lca (a,b);
return (deep[a]+deep[b]-2*deep[anc]);
}
int main(){
scanf("%d%d",&n,&q);
for (int i=2;i<=n;i++){
int temp;
scanf("%d",&temp);
E[temp].push_back(i);
E[i].push_back(temp);
}
build (1,0,1);
initLCA();
while (q--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int anc1 = lca(a,b);
int anc2 = lca(a,c);
int anc3 = lca(b,c);
if (deep[anc2]>deep[anc1]){
anc1 = anc2;
}
if (deep[anc3]>deep[anc1]){
anc1 = anc3;
}
int ans1 = length(anc1,a);
int ans2 = length(anc1,b);
int ans3 = length(anc1,c);
int ans = max (max ( ans1,ans2),ans3)+1;
printf("%d\n",ans);
}
return 0;
}