[AHOI2008]紧急集合 / 聚会

传送门

这题题面有毒,其实就是让你求将树上三个点都移到任意一点,三个点最少一共经过多少条边。

在某种意义上可以理解为三个点的LCA?

显然我们会将这三个点移到其中两个点的LCA上去。那么具体选择哪个LCA呢?

我们发现,要么三个点的LCA都相同,这个时候只有一种选择;绝对不可能出现三个点的LCA都不同的情况。很容易证明,这里略去。

那么如果有两个点的LCA相同呢?我们会选择那个“与众不同”的LCA。

因为我们如果选这两个相同的LCA做三个点的相遇点,就会产生两个点经过了重复的边的情况,血亏啊

结合一张图来理解:盗的

在这里插入图片描述
如果要2,5,8集合,(2,8)和(5,8)LCA都是1,就会导致无辜的2和5这两个小盆友重复经过 1 → 2 1 \rightarrow 2 12 这条边,我们肯定愿意让吃苦耐劳的8走这一条边和2,5回合,而不愿意让2,5两个人去顺从8一个人啊众所周知少数服从多数

讲了这么多差不多了吧QAQ,其实这道题还是挺水的

注:在 M r . c o d e Mr.code Mr.code 中我虽然在洛谷随便怎么都能过,但是由于最大的点跑了897ms,为了防止在其它跑得慢的 O J OJ OJ 上超时,我卡了卡常数,效果显著:(最后一次提交开了O2)
在这里插入图片描述

C o d e : Code: Code:

#include <cstdio>
#include <cmath>
#include <vector>
#define MAXN 500005
#define GetDis(x, y) (Dep[x] + Dep[y] - (Dep[LCA(x, y)] << 1))
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 18, stdin), p1 == p2) ? EOF : *p1 ++ )

char buf[1 << 18], *p1, *p2;

inline int read() {
	char ch;
	int x(0);
	while ((ch = gc) < 48);
	while (ch >= 48) x = x * 10 + ch - 48, ch = gc;
	return x;
}

int fa[MAXN][20], Dep[MAXN], tot, n;
std::vector<int> sons[MAXN];

inline void dfs(int u) {
	int MAX(ceil(log2(Dep[u] = Dep[fa[u][0]] + 1)));
	for (int i(1); i <= MAX; ++ i)
	fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (auto i : sons[u]) if (i != fa[u][0]) fa[i][0] = u, dfs(i);
}

inline int LCA(int x, int y) {
	if (Dep[x] < Dep[y]) {int t(x); x = y, y = t;}
	int MAX(ceil(log2(n)));
	for (int i(0); i <= MAX; ++ i) if (Dep[x] - Dep[y] & 1 << i) x = fa[x][i];
	if (x == y) return x;
	for (int i(ceil(log2(Dep[x]))); i >= 0; -- i)
	if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}

inline void cmp(int x, int y, int z) {
	int t1(LCA(x, y)), t2(LCA(x, z)), t3(LCA(y, z));
	if (t1 == t2 && t2 != t3)
		printf("%d %d\n", t3, Dep[y] + Dep[z] - (Dep[t3] << 1) + GetDis(x, t3));
	else if (t1 == t3 && t2 != t3)
		printf("%d %d\n", t2, Dep[x] + Dep[z] - (Dep[t2] << 1) + GetDis(y, t2));
	else printf("%d %d\n", t1, Dep[x] + Dep[y] - (Dep[t1] << 1) + GetDis(z, t1));
}

int main() {
	n = read();
	int m(read());
	for (int i(1); i < n; ++ i) {
		int x(read()), y(read());
		sons[x].push_back(y), sons[y].push_back(x);
	}
	dfs(1);
	while (m --) cmp(read(), read(), read());
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值