一个求树上LCA的裸题。。WC(伪。。)里想出来的。。
题目大意:给出树上的三个点,要求确定一个集合点,使得这三个点到集合点的路径权值和最小。所有边权均为1。
先考虑两个点A、B的情形。。显然这两个点间路径上的任何一点都可以作为集合点。。
然后再加入第三个点C。。画个图不难证明此时最优集合点应是LCA(A, B)。
但是A、B、C分别是哪个点呢?。。
枚举取最优解。。
但看了黄学长博客,给出了两种解法。。一种就是枚举,另一种则给出了一句结论,私认为很有道理。。
“求出两两lca,其中有两个相同,答案则为另一个,画画图就可以理解”
这样的话,就可以直接确定是那一个LCA。。不用判断了。。
所以答案就很好搞了。。求求LCA加加就行了。。
用的是黄学长的模板。。
// BZOJ 1787
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
const int N=500000+5, INF=0x3f3f3f3f;
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define dep(i,a,b) for (int i=a; i>=b; i--)
#define read(x) scanf("%d", &x)
#define fill(a,x) memset(a, x, sizeof(a))
int n, q, dep[N], last[N], fa[N][20];
struct Edge {
int to, pre;
} e[N*2]; // 注意无向边数要乘2!
int k=0;
void ine(int x, int y) {
k++;
e[k].to=y; e[k].pre=last[x]; last[x]=k;
k++;
e[k].to=x; e[k].pre=last[y]; last[y]=k;
}
#define reg(i,a) for (int i=last[a]; i; i=e[i].pre)
void change(int x, int dad) { // 无根树转有根树的同时递推每个点的2^i祖先
rep(i,1,18) {
if (dep[x]<(1<<i)) break;
fa[x][i]=fa[fa[x][i-1]][i-1];
}
reg(i,x) {
int y=e[i].to;
if (y==dad) continue;
dep[y]=dep[x]+1;
fa[y][0]=x;
change(e[i].to, x);
}
}
int LCA(int x, int y) {
if (dep[x]<dep[y]) swap(x, y);
int d=dep[x]-dep[y];
rep(i,0,18)
if ((1<<i)&d) x=fa[x][i];
dep(i,18,0)
if (fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; }
if (x==y) return x; else return fa[x][0];
}
int dis(int x, int y) { int t=LCA(x,y); return dep[x]+dep[y]-2*dep[t]; }
void solve(int x, int y, int z) {
int p1=LCA(x, y), p2=LCA(x, z), p3=LCA(y, z), p, ans;
if (p1==p2) p=p3; else if (p2==p3) p=p1; else p=p2;
ans=dis(x, p)+dis(y, p)+dis(z, p);
printf("%d %d\n", p, ans);
}
int main()
{
read(n); read(q);
rep(i,1,n-1) {
int u, v;
read(u); read(v);
ine(u, v);
}
change(1, 0);
rep(i,1,q) {
int x, y, z;
read(x); read(y); read(z);
solve(x, y, z);
}
return 0;
}