AHOI2008 紧急集合 树上倍增

AHOI2008 紧急集合

题目传送

sol:

如果只有两个点,那么显然目的地就是在他们二者路径上的任意一点。

现在有三个点,考虑两两的路径和lca,发现肯定有两对求得的lca相同,另外一对的lca深度比那两对的lca深度大。

这个深度大一些的那个lca就是目的地(最近点),最小距离就是三者两两距离的二分之一。

a

所以直接树上倍增即可。

#include<bits/stdc++.h>
#define IL inline
#define RG register
#define DB double
#define LL long long
using namespace std;

IL int gi() {
	RG int x=0,p=1; RG char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') p=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
	return x*p;
}

const int N=5e5+1;

int n,m,tot,len,npos,dep[N],head[N],f[N][20];

struct LCA{int pos,dis;}s[5];
struct EDGE{int next,to;}e[N<<1];

IL void make(int a,int b) {
	e[++tot]=(EDGE){head[a],b},head[a]=tot;
	e[++tot]=(EDGE){head[b],a},head[b]=tot;
}

void dfs(int x,int fx) {
	RG int i,y;
	for(i=head[x];i;i=e[i].next)
		if((y=e[i].to)!=fx)
			dep[y]=dep[x]+1,f[y][0]=x,dfs(y,x);
}

IL void multiply() {
	RG int i,j;
	for(i=1;i<20;++i)
		for(j=1;j<=n;++j)
			f[j][i]=f[f[j][i-1]][i-1];
}

IL LCA lca(int x,int y) {
	RG int i,dis=0;
	if(dep[x]<dep[y]) swap(x,y);
	for(i=19;i>=0;--i)
		if(dep[f[x][i]]>=dep[y]) dis+=1<<i,x=f[x][i];
	if(x==y) return (LCA){x,dis};
	for(i=19;i>=0;--i)
		if(f[x][i]!=f[y][i])
			dis+=1<<i+1,x=f[x][i],y=f[y][i];
	return (LCA){f[x][0],dis+2};
}

int main()
{
	RG int i,x,y,z;
	n=gi(),m=gi();
	for(i=1;i<n;++i) x=gi(),y=gi(),make(x,y);
	dep[1]=1,dfs(1,1),multiply();
	while(m--) {
		x=gi(),y=gi(),z=gi();
		s[1]=lca(x,y),s[2]=lca(y,z),s[3]=lca(x,z);
		len=(s[1].dis+s[2].dis+s[3].dis)/2;
		for(i=1,npos=0;i<=3;++i)
			if(dep[s[i].pos]>dep[npos]) npos=s[i].pos;
		printf("%d %d\n",npos,len);
	}
	return 0;
}

posted @ 2019-07-26 22:28  薄荷凉了夏  阅读(182)  评论(0编辑  收藏  举报