这道题目有两个版本的题面(
l
o
l
lol
lol),不过题目的意思是一样的。
这道题给出了一棵
n
n
n个点的树和
m
m
m个询问,对于每个询问给出三个点,求这棵树上到这三个点距离之和最小的点以及最小距离和。
我们先来考虑一个简化版本:每次不是询问三个点,而是两个。那么答案显而易见:合法的点为这两个点的 l c a lca lca到这两个点路径上的任意一点,最短距离和也就是这两个点的距离。
好了,那么我们回到这个问题,给你三个点怎么办。根据我们刚刚得出的结论,答案就在这三个 l c a lca lca到这三个点的路径上。而 l c a lca lca有一个性质:对树上任意三个点两两求 l c a lca lca,这三个 l c a lca lca至少有两个相同,且如果有两个 l c a lca lca相同,另外一个 l c a lca lca的深度一定是最大的。
我们设询问的三个点分别为 a a a、 b b b、 c c c, l c a ( a , b ) lca(a,b) lca(a,b)为 x x x, l c a ( a , c ) lca(a,c) lca(a,c)和 l c a ( b , c ) lca(b,c) lca(b,c)均为 y y y。那么答案所求的点一定是 x x x。为什么呢?首先我们不难发现, x x x一定在 y y y到 a a a和 b b b的路径上。令 a n s ans ans表示将 x x x作为集合点时的距离和, d [ i ] d[i] d[i]表示点 i i i的深度,那么显然 a n s = a ans=a ans=a到 b b b的距离 + c +c +c到 x x x的距离 = d [ a ] + d [ b ] − 2 d [ x ] + d [ x ] + d [ c ] − 2 d [ y ] = d [ a ] + d [ b ] + d [ c ] − d [ x ] − 2 d [ y ] 。 =d[a]+d[b]-2d[x]+d[x]+d[c]-2d[y]=d[a]+d[b]+d[c]-d[x]-2d[y]。 =d[a]+d[b]−2d[x]+d[x]+d[c]−2d[y]=d[a]+d[b]+d[c]−d[x]−2d[y]。我们刚刚说过答案点在 y y y到 a a a、 b b b、 c c c的路径上,那么我们分类讨论一下:
1、如果答案点 p p p在 a a a到 b b b的路径上,那么距离和为 d [ a ] + d [ b ] − 2 d [ x ] + d [ p ] + d [ c ] − 2 d [ y ] d[a]+d[b]-2d[x]+d[p]+d[c]-2d[y] d[a]+d[b]−2d[x]+d[p]+d[c]−2d[y],对比上面 a n s ans ans的式子可以发现这个距离和一定大于 a n s ans ans,因为显然 d [ p ] > d [ x ] d[p]>d[x] d[p]>d[x]。
2、如果答案点 p p p在 c c c到 a a a或 b b b的路径上,那么距离和为 d [ a ] + d [ b ] − 2 d [ p ] + d [ p ] + d [ c ] − 2 d [ y ] = d [ a ] + d [ b ] + d [ c ] − d [ p ] − 2 d [ y ] d[a]+d[b]-2d[p]+d[p]+d[c]-2d[y]=d[a]+d[b]+d[c]-d[p]-2d[y] d[a]+d[b]−2d[p]+d[p]+d[c]−2d[y]=d[a]+d[b]+d[c]−d[p]−2d[y],对比上面的式子,因为 d [ p ] < d [ x ] d[p]<d[x] d[p]<d[x],所以这个距离和也大于 a n s ans ans。
现在我们证明了答案一定为 x x x。如果我们再观察一下 a n s ans ans的式子可以发现,因为 y y y既为 l c a ( a , c ) lca(a,c) lca(a,c),又为 l c a ( b , c ) lca(b,c) lca(b,c),所以 a n s ans ans可以转换成 d [ a ] + d [ b ] + d [ c ] − d [ l c a ( a , b ) ] − d [ l c a ( a , c ) ] − d [ l c a ( b , c ) ] d[a]+d[b]+d[c]-d[lca(a,b)]-d[lca(a,c)]-d[lca(b,c)] d[a]+d[b]+d[c]−d[lca(a,b)]−d[lca(a,c)]−d[lca(b,c)],这样就免去了一个分类讨论。
另外就是这道题的数据范围比较大,推荐树剖求 l c a lca lca,倍增的话可能需要卡一下常数(至少卡常之后可以在洛谷过去)
倍增:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,top,h[N],to[N<<1],pre[N<<1],d[N],fa[N][22];
int read()//快读
{
int sum=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
return sum;
}
void ins(int u,int v)//建边
{
pre[++top]=h[u];h[u]=top;to[top]=v;
}
void dfs(int x)
{
for(int i=h[x];i;i=pre[i])
{
int v=to[i];
if(v==fa[x][0])continue;
fa[v][0]=x;d[v]=d[x]+1;dfs(v);
}
}
void bz()
{
for(int j=1;j<22;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int lca(int u,int v)
{
if(d[u]<d[v])swap(u,v);
for(int i=21;i>=0;i--)
if(d[fa[u][i]]>=d[v])u=fa[u][i];
if(u==v)return u;
for(int i=21;i>=0;i--)
if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int main()
{
n=read();m=read();d[1]=1;
for(int i=1;i<n;i++)
{
int x=read(),y=read();
ins(x,y);ins(y,x);
}
dfs(1);bz();//倍增lca的预处理,应该不用多讲吧qwq
while(m--)
{
int x=read(),y=read(),z=read();
int ll,l1=lca(x,y),l2=lca(x,z),l3=lca(y,z);
if(l1==l2)ll=l3;
else if(l1==l3)ll=l2;
else ll=l1;//分类讨论求出答案点
printf("%d %d\n",ll,d[x]+d[y]+d[z]-d[l1]-d[l2]-d[l3]);
}
return 0;
}
树剖:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,tot,fa[N],h[N],pre[N<<1],to[N<<1],size[N],son[N],top[N],d[N];
int read()//快读
{
int sum=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
return sum;
}
void ins(int u,int v)//建边
{
pre[++tot]=h[u];h[u]=tot;to[tot]=v;
}
void dfs1(int x)
{
int maxn=0,mson;
size[x]=1;
for(int i=h[x];i;i=pre[i])
{
int v=to[i];
if(v==fa[x])continue;
fa[v]=x;d[v]=d[x]+1;dfs1(v);size[x]+=size[v];
if(size[v]>maxn)maxn=size[v],mson=v;
}
son[x]=mson;
}
void dfs2(int x,int tp)
{
top[x]=tp;
for(int i=h[x];i;i=pre[i])
{
int v=to[i];
if(v==fa[x])continue;
if(v==son[x])dfs2(v,tp);else dfs2(v,v);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])//不停的在重链上跳直到两个点在一条重链上
{
if(d[top[u]]<d[top[v]])swap(u,v);
u=fa[top[u]];
}
if(d[u]<d[v])return u;
return v;
}
int main()
{
n=read();m=read();d[1]=1;
for(int i=1;i<n;i++)
{
int x=read(),y=read();
ins(x,y);ins(y,x);
}
dfs1(1);dfs2(1,1);//树剖的预处理
while(m--)
{
int x=read(),y=read(),z=read();
int ll,l1=lca(x,y),l2=lca(x,z),l3=lca(y,z);
if(l1==l2)ll=l3;
else if(l1==l3)ll=l2;
else ll=l1;//分类讨论求出答案点
printf("%d %d\n",ll,d[x]+d[y]+d[z]-d[l1]-d[l2]-d[l3]);
}
return 0;
}