题意
有一颗树,多次询问:给出三个节点,求节点(k)使距离之和最小,且求距离。
思路
(lca)裸题。
这里可以证明一个性质:两两(lca)会得出三个节点,其中至少有两个重合。
证明:
显然有三种分布情况:
-
三个节点都在同一颗子树上,这时公共(lca)显然为子树根。
-
两个节点在同一颗子树上。假设这两个点((a,b))的(lca)为(x),而另一个节点为(y),那么(lca(a,y)=lca(b,y)=lca(x,y))。显然有两个重合。
-
三个节点都在不同子树上,同第一种情况。
(Q.E.D)
有了上面的性质,我们在计算的时候可以稍微减少计算量,
(ans=dep[a]+dep[b]+dep[c]-dep[lca(a,b)]+dep[lca(b,c)]+dep[lca(c,a)])
但是这样还是过不了,因为(OJ)卡倍增(lca),所以必须使用树剖求(lca)。
代码
(倍增90分)
#include <bits/stdc++.h>
using namespace std;
namespace StandardIO {
template<typename T> inline void read (T &x) {
x=0;T f=1;char c=getchar();
for (; c<'0'||c>'9'; c=getchar()) if (c=='-') f=-1;
for (; c>='0'&&c<='9'; c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline void write (T x) {
if (x<0) putchar('-'),x=-x;
if (x>=10) write(x/10);
putchar((x%10)+'0');
}
}
using namespace StandardIO;
namespace Solve {
const int N=500500;
int n,m;
int cnt;
int head[N];
struct node {
int to,next;
} edge[N<<1];
int fa[N][31],dep[N];
inline void add (int a,int b) {
edge[++cnt].to=b,edge[cnt].next=head[a],head[a]=cnt;
}
void dfs (int now,int f) {
fa[now][0]=f,dep[now]=dep[f]+1;
for (register int i=1; (1<<i)<=dep[now]; ++i) {
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for (register int i=head[now]; i; i=edge[i].next) {
int to=edge[i].to;
if (dep[to]) continue;
dfs(to,now);
}
}
inline int lca (int x,int y) {
if (dep[x]>dep[y]) swap(x,y);
for (register int i=20; i>=0; --i) {
if (dep[y]>=dep[x]+(1<<i)) y=fa[y][i];
}
if (x==y) return x;
for (register int i=20; i>=0; --i) {
if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
}
return fa[x][0];
}
inline void Main () {
read(n),read(m);
for (register int i=1; i<=n-1; ++i) {
int a,b;
read(a),read(b);
add(a,b),add(b,a);
}
dfs(1,0);
for (register int i=1; i<=m; ++i) {
int a,b,c,ab,bc,ca,ans,dis;
read(a),read(b),read(c);
ab=lca(a,b),bc=lca(b,c),ca=lca(c,a);
if (ab==bc) ans=ca;
else if (ab==ca) ans=bc;
else if (bc==ca) ans=ab;
dis=dep[a]+dep[b]+dep[c]-dep[ab]-dep[bc]-dep[ca];
write(ans),putchar(' '),write(dis),putchar('
');
}
}
}
int main () {
Solve::Main();
}