P4281 [AHOI2008]紧急集合 / 聚会(最近公共祖先)

传送门

  • 假设一棵树上要求两个点的到某个点的最短路径,那么可以知道两个点到其两者的LCA距离和一定是最短的。

  • 不难得到,三个点两两相互的LCA点一定有一个是一样的,进而可以发现,如果三个点要去那两个一样的LCA的其中一个,会导致第三个点走重复的路程,这样就不能保证是最短的了。

  • 如果要保证路程最短,那么也就是尽量要减少重复走的路程,可以发现,如果三点同时走那个唯一的LCA,则没有重复路程,这样距离之和就是最短的。

  • 那么如何求最短的距离,对于两个点的距离来说,设两个点分别是a,b,那么其距离为depth[a] + depth[b] - 2 * dep[LCAab]对于这道题的三个点两两求一下就好了。

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
const int M = 1e6 + 10;
int head[N], tot, n, m, fa[N][22], dep[N];

struct Edge{
	int v, next;
}edge[M];

void add(int u, int v){
	edge[tot].next = head[u];
	edge[tot].v = v;
	head[u] = tot ++;
}

void dfs(int now, int pre){
	dep[now] = dep[pre] + 1;
	fa[now][0] = pre;
	for(int i = head[now]; i != -1; i = edge[i].next){
		int v = edge[i].v;
		if(v != pre)
			dfs(v, now);
	}
}

int getlca(int u, int v){
	if(dep[u] < dep[v])
		swap(u, v);
	while(dep[u] != dep[v])
		u = fa[u][(int)log2(dep[u] - dep[v])];
	if(u == v)
		return u;
	for(int i = 20; i >= 0; i --)
		if(fa[u][i] != fa[v][i])
			u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int main(){
	scanf("%d%d", &n, &m);
	memset(head, -1, sizeof head);
	for(int i = 1; i < n; i ++){
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
	}	
	dfs(1, 0);
	for(int j = 1; j <= 20; j ++)
		for(int i = 1; i <= n; i ++)
			fa[i][j] = fa[fa[i][j - 1]][j - 1];
	
	for(int i = 1; i <= m; i ++){
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		int lca1 = getlca(a, b);
		int lca2 = getlca(a, c);
		int lca3 = getlca(b, c);
		int unique = 0;
		if(lca1 == lca2)
			unique = lca3;
		else if(lca1 == lca3)
			unique = lca2;
		else
			unique = lca1;
		int ans = dep[a] + dep[b] + dep[c] - dep[lca1] - dep[lca2] - dep[lca3];
		printf("%d %d
", unique, ans);
	}
	
	return 0;
}
原文地址:https://www.cnblogs.com/pureayu/p/14979659.html