[ABC014D] 閉路 题解

[ABC014D] 閉路

几乎是一道模板题。

读本篇题解需要了解的知识

  • 什么是最近公共祖先。

  • 重链剖分求最近公共祖先。

这两个知识点下文不再解释,没有掌握的同学请自行上网学习。

解法

题中给出一颗 n n n 个顶点的树,给出 q q q 组询问,每次询问给定 u u u v v v,表示在顶点 u u u 和顶点 v v v 之间添加一条边,对于每次询问,输出包含顶点 u u u 和顶点 v v v 之间的边的环中边的个数。

关于树的性质:

  • 一棵树上是没有环的。

  • 在树上添加一条边肯定会生成一个环。

考虑我们在顶点 u u u 和顶点 v v v 之间添加一条边后生成的这个环,这个环由两部分构成,一部分是新添加的一条边,另一部分是在原来树上从 u u u v v v 的简单路径。

再来看这个简单路径,这个路径包含了从 u u u 到两顶点的最近公共祖先的距离(这里即指简单路径长度,下文同)和 v v v 到两顶点的最近公共祖先的距离,举例如图所示,用 graph_editor 作图。

考虑 u = 5 , v = 3 u = 5,v = 3 u=5,v=3 的情况,在顶点 5 5 5 3 3 3 之间添加一条边,此时这个环的边的数量为 4 4 4。其中包括顶点 1 1 1(顶点 5 5 5 3 3 3 的最近公共祖先)到顶点 5 5 5 的距离,顶点 1 1 1 到顶点 3 3 3 的距离和新添加的这条边。

我们可以钦定一个点为这棵树的树根,从这个点开始两次深搜后得到最近公共祖先,两个点到它们的最近公共祖先的距离分别是两个点和它们的最近公共祖先的深度的差,所以对于每次询问我们只要输出 d e p u − d e p L C A + d e p v − d e p L C A + 1 = d e p u + d e p v − 2 × d e p L C A + 1 dep_u - dep_{LCA} + dep_v - dep_{LCA} + 1 = dep_u + dep_v - 2 \times dep_{LCA} +1 depudepLCA+depvdepLCA+1=depu+depv2×depLCA+1 即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define Getchar() p1==p2 and (p2=(p1=Inf)+fread(Inf,1,1<<21,stdin),p1==p2)?EOF:*p1++
#define Putchar(c) p3==p4 and (fwrite(Ouf,1,1<<21,stdout),p3=Ouf),*p3++=c
char Inf[1<<21],Ouf[1<<21],*p1,*p2,*p3=Ouf,*p4=Ouf+(1<<21);
inline void read(int &x,char c=Getchar())
{
	bool f=c!='-';
	x=0;
	while(c<48 or c>57) c=Getchar(),f&=c!='-';
	while(c>=48 and c<=57) x=(x<<3)+(x<<1)+(c^48),c=Getchar();
	x=f?x:-x;
}
inline void write(int x)
{
	if(x<0) Putchar('-'),x=-x;
	if(x>=10) write(x/10),x%=10;
	Putchar(x^48);
}
int n,q,cnt,head[100010],dep[100010],f[100010],siz[100010],hvson[100010],start[100010];
struct edge
{
	int to,next;
};
edge e[200010];
inline void add(const int &x,const int &y)
{
	e[++cnt].to=y,e[cnt].next=head[x],head[x]=cnt;
}
inline void dfs1(int pos,int fa,int depth,int maxi=-0x3f3f3f3f)
{
	dep[pos]=depth,f[pos]=fa,siz[pos]=1;
	for(int i=head[pos];i;i=e[i].next)
	{
		if(e[i].to!=fa)
		{
			dfs1(e[i].to,pos,depth+1),siz[pos]+=siz[e[i].to];
			if(siz[e[i].to]>maxi) hvson[pos]=e[i].to,maxi=siz[e[i].to];
		}
	}
}
inline void dfs2(int pos,int Start)
{
	start[pos]=Start;
	if(hvson[pos])
	{
		dfs2(hvson[pos],Start);
		for(int i=head[pos];i;i=e[i].next)
			if(e[i].to!=f[pos] && e[i].to!=hvson[pos]) dfs2(e[i].to,e[i].to);
	}
}
inline int lca(int x,int y)
{
	while(start[x]!=start[y])
		if(dep[start[x]]>=dep[start[y]]) x=f[start[x]];
		else y=f[start[y]];
	if(dep[x]>=dep[y]) return y;
	else return x;
}
int main()
{
	read(n);
	for(int i=1,x,y;i<n;i++) read(x),read(y),add(x,y),add(y,x);
	read(q),dfs1(1,0,1),dfs2(1,1);
	for(int i=1,x,y;i<=q;i++) read(x),read(y),write(dep[x]+dep[y]-2*dep[lca(x,y)]+1),Putchar('\n');
	fwrite(Ouf,1,p3-Ouf,stdout),fflush(stdout);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值