bzoj 1787(LCA)

传送门

题意:一棵树 N 个结点的树,有 M 次查询:树上三个点到哪个点的距离之和最小。

题解:

求(a,b)(b,c)(c,a)三对点的LCA,深度最深的那个即使答案:

下面口胡一下证明过程:

三个LCA如果发生重合,那么显然上述做法正确,不再赘述。

如果三个LCA互不相同,那么它们一定可以组成一个二叉结构,从将最浅的LCA作为答案,将其往另外两个移动,每移动一次距离之和+1-2。显然移动到最深的那个LCA可以最小化距离之和,到达最深LCA后若再移动则反而会增大距离之和,所以最深LCA即所求点。


P.S.为数不多的本蒟蒻卡不下来的大常数代码,估计跑得比我快1/3的都是tarjan求LCA而非倍增。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=5e5+4;
int head[MAXN],etot=0;
struct EDGE {
	int v,nxt;
}e[MAXN<<1];
int n,m,f[20][MAXN],dep[MAXN]={0};
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
inline void adde(int u,int v) {
	e[etot].nxt=head[u],e[etot].v=v,head[u]=etot++;
	e[etot].nxt=head[v],e[etot].v=u,head[v]=etot++;
}
void dfs(int p,int fa) {
	dep[p]=dep[fa]+1,f[0][p]=fa;
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (v^fa) dfs(v,p);
	}
}
inline void da() {
	for (int j=1;(1<<j)<=n;++j)
		for (register int i=1;i<=n;++i)
			f[j][i]=f[j-1][f[j-1][i]];
}
inline int LCA(int x,int y) {
	if (dep[x]<dep[y]) x^=y^=x^=y;
	int t=dep[x]-dep[y];
	for (int i=0;i<20;++i)
		if (t&(1<<i)) x=f[i][x];
	if (x==y) return x;
	for (int i=19;~i;--i)
		if (f[i][x]^f[i][y]) x=f[i][x],y=f[i][y];
	return f[0][x];
}
inline int dis(int x,int y) {
	return dep[x]+dep[y]-(dep[LCA(x,y)]<<1);
}
int main() {
//	freopen("bzoj 1787.in","r",stdin);
	memset(head,-1,sizeof(head));
	n=read(),m=read();
	for (register int i=1;i<n;++i) {
		int u=read(),v=read();
		adde(u,v);
	}
	dfs(1,0);
	da();
	for (register int t=0;t<m;++t) {
		int a=read(),b=read(),c=read();
		int x=LCA(a,b),y=LCA(b,c),z=LCA(c,a);
		if (dep[x]>=dep[y]&&dep[x]>=dep[z]) {printf("%d %d\n",x,dep[a]-dep[x]+dep[b]-dep[x]+dis(x,c));continue;}
		if (dep[y]>=dep[x]&&dep[y]>=dep[z]) {printf("%d %d\n",y,dep[b]-dep[y]+dep[c]-dep[y]+dis(y,a));continue;}
		if (dep[z]>=dep[x]&&dep[z]>=dep[y]) {printf("%d %d\n",z,dep[c]-dep[z]+dep[a]-dep[z]+dis(z,b));continue;}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值