洛谷P4281【AHOI2008】紧急集合/聚会

1 篇文章 0 订阅
0 篇文章 0 订阅

题目传送门

这道题目有两个版本的题面( l o l lol lol),不过题目的意思是一样的。
这道题给出了一棵 n n n个点的树和 m m m个询问,对于每个询问给出三个点,求这棵树上到这三个点距离之和最小的点以及最小距离和。

我们先来考虑一个简化版本:每次不是询问三个点,而是两个。那么答案显而易见:合法的点为这两个点的 l c a lca lca到这两个点路径上的任意一点,最短距离和也就是这两个点的距离。

好了,那么我们回到这个问题,给你三个点怎么办。根据我们刚刚得出的结论,答案就在这三个 l c a lca lca到这三个点的路径上。而 l c a lca lca有一个性质:对树上任意三个点两两求 l c a lca lca,这三个 l c a lca lca至少有两个相同,且如果有两个 l c a lca lca相同,另外一个 l c a lca lca的深度一定是最大的。

我们设询问的三个点分别为 a a a b b b c c c l c a ( a , b ) lca(a,b) lca(a,b) x x x l c a ( a , c ) lca(a,c) lca(a,c) l c a ( b , c ) lca(b,c) lca(b,c)均为 y y y。那么答案所求的点一定是 x x x。为什么呢?首先我们不难发现, x x x一定在 y y y a a a b b b的路径上。令 a n s ans ans表示将 x x x作为集合点时的距离和, d [ i ] d[i] d[i]表示点 i i i的深度,那么显然 a n s = a ans=a ans=a b b b的距离 + c +c +c x x x的距离 = d [ a ] + d [ b ] − 2 d [ x ] + d [ x ] + d [ c ] − 2 d [ y ] = d [ a ] + d [ b ] + d [ c ] − d [ x ] − 2 d [ y ] 。 =d[a]+d[b]-2d[x]+d[x]+d[c]-2d[y]=d[a]+d[b]+d[c]-d[x]-2d[y]。 =d[a]+d[b]2d[x]+d[x]+d[c]2d[y]=d[a]+d[b]+d[c]d[x]2d[y]我们刚刚说过答案点在 y y y a a a b b b c c c的路径上,那么我们分类讨论一下:

1、如果答案点 p p p a a a b b b的路径上,那么距离和为 d [ a ] + d [ b ] − 2 d [ x ] + d [ p ] + d [ c ] − 2 d [ y ] d[a]+d[b]-2d[x]+d[p]+d[c]-2d[y] d[a]+d[b]2d[x]+d[p]+d[c]2d[y],对比上面 a n s ans ans的式子可以发现这个距离和一定大于 a n s ans ans,因为显然 d [ p ] > d [ x ] d[p]>d[x] d[p]>d[x]

2、如果答案点 p p p c c c a a a b b b的路径上,那么距离和为 d [ a ] + d [ b ] − 2 d [ p ] + d [ p ] + d [ c ] − 2 d [ y ] = d [ a ] + d [ b ] + d [ c ] − d [ p ] − 2 d [ y ] d[a]+d[b]-2d[p]+d[p]+d[c]-2d[y]=d[a]+d[b]+d[c]-d[p]-2d[y] d[a]+d[b]2d[p]+d[p]+d[c]2d[y]=d[a]+d[b]+d[c]d[p]2d[y],对比上面的式子,因为 d [ p ] &lt; d [ x ] d[p]&lt;d[x] d[p]<d[x],所以这个距离和也大于 a n s ans ans

现在我们证明了答案一定为 x x x。如果我们再观察一下 a n s ans ans的式子可以发现,因为 y y y既为 l c a ( a , c ) lca(a,c) lca(a,c),又为 l c a ( b , c ) lca(b,c) lca(b,c),所以 a n s ans ans可以转换成 d [ a ] + d [ b ] + d [ c ] − d [ l c a ( a , b ) ] − d [ l c a ( a , c ) ] − d [ l c a ( b , c ) ] d[a]+d[b]+d[c]-d[lca(a,b)]-d[lca(a,c)]-d[lca(b,c)] d[a]+d[b]+d[c]d[lca(a,b)]d[lca(a,c)]d[lca(b,c)],这样就免去了一个分类讨论。

另外就是这道题的数据范围比较大,推荐树剖求 l c a lca lca,倍增的话可能需要卡一下常数(至少卡常之后可以在洛谷过去)

倍增:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,top,h[N],to[N<<1],pre[N<<1],d[N],fa[N][22];
int read()//快读 
{
	int sum=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
	return sum;
}
void ins(int u,int v)//建边 
{
	pre[++top]=h[u];h[u]=top;to[top]=v;
}
void dfs(int x)
{
	for(int i=h[x];i;i=pre[i])
	{
		int v=to[i];
		if(v==fa[x][0])continue;
		fa[v][0]=x;d[v]=d[x]+1;dfs(v);
	}
}
void bz()
{
	for(int j=1;j<22;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
}
int lca(int u,int v)
{
	if(d[u]<d[v])swap(u,v);
	for(int i=21;i>=0;i--)
		if(d[fa[u][i]]>=d[v])u=fa[u][i];
	if(u==v)return u;
	for(int i=21;i>=0;i--)
		if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
int main()
{
	n=read();m=read();d[1]=1;
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		ins(x,y);ins(y,x);
	}
	dfs(1);bz();//倍增lca的预处理,应该不用多讲吧qwq 
	while(m--)
	{
		int x=read(),y=read(),z=read();
		int ll,l1=lca(x,y),l2=lca(x,z),l3=lca(y,z);
		if(l1==l2)ll=l3;
		else if(l1==l3)ll=l2;
		else ll=l1;//分类讨论求出答案点 
		printf("%d %d\n",ll,d[x]+d[y]+d[z]-d[l1]-d[l2]-d[l3]);
	}
	return 0;
}

树剖:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,tot,fa[N],h[N],pre[N<<1],to[N<<1],size[N],son[N],top[N],d[N];
int read()//快读 
{
	int sum=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
	return sum;
}
void ins(int u,int v)//建边 
{
	pre[++tot]=h[u];h[u]=tot;to[tot]=v;
}
void dfs1(int x)
{
	int maxn=0,mson;
	size[x]=1;
	for(int i=h[x];i;i=pre[i])
	{
		int v=to[i];
		if(v==fa[x])continue;
		fa[v]=x;d[v]=d[x]+1;dfs1(v);size[x]+=size[v];
		if(size[v]>maxn)maxn=size[v],mson=v;
	}
	son[x]=mson;
}
void dfs2(int x,int tp)
{
	top[x]=tp;
	for(int i=h[x];i;i=pre[i])
	{
		int v=to[i];
		if(v==fa[x])continue;
		if(v==son[x])dfs2(v,tp);else dfs2(v,v);
	}
}
int lca(int u,int v)
{
	while(top[u]!=top[v])//不停的在重链上跳直到两个点在一条重链上 
	{
		if(d[top[u]]<d[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	if(d[u]<d[v])return u;
	return v;
}
int main()
{
	n=read();m=read();d[1]=1;
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		ins(x,y);ins(y,x);
	}
	dfs1(1);dfs2(1,1);//树剖的预处理 
	while(m--)
	{
		int x=read(),y=read(),z=read();
		int ll,l1=lca(x,y),l2=lca(x,z),l3=lca(y,z);
		if(l1==l2)ll=l3;
		else if(l1==l3)ll=l2;
		else ll=l1;//分类讨论求出答案点 
		printf("%d %d\n",ll,d[x]+d[y]+d[z]-d[l1]-d[l2]-d[l3]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值