BZOJ 1787 裸LCA
一个求树上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; }