【XSY2597】一样远(equal)(LCA)

(Description)

企鹅国的城市结构是一棵树,有(N)座城市和(N-1)条无向道路,每条道路都一样长。豆豆和豆沙准备去参加(NOIP(National Olympiad in Informatics for Penguin)),但是他们住在不同的地方,豆豆住在城市(A),豆沙住在城市(B)。他们想找一个距离(A)(B)一样远的集合地点,所以他们想知道有多少个城市满足这个要求?

由于他们会参加很多次(NOIP),所以有很多个询问。


(Input)

第一行一个整数(N)代表城市个数。

接下来(N-1)行,每行两个数字(F)(T),表示城市(F)和城市(T)之间有一条道路。

接下来一行一个整数(M)代表询问次数。

接下来(M)行,每行两个数字(A)(B),表示这次询问的城市(A)和城市(B)(A)可能与(B)相同)。


(Output)

输出(M)行,每行一个整数表示到(A)(B)一样远的城市个数。


(SampleInput1)

4

1 2

2 3

2 4

2

1 2

1 3


(SampleOutput1)

0

2


(SampleInput2)

4

1 2

2 3

2 4

2

1 1

3 3


(SampleOutput2)

4

4


(Hint)

对于(30\%)的数据:(N,M≤1000)

对于另外(10\%)的数据:(A=B)

对于另外(30\%)的数据:保证树的形态随机;

对于(100\%)的数据:(1≤N,M≤100000)


思路

我们发现这道题给出的一棵树,于是我们考虑树上路径的算法:(LCA),树链剖分

我们在这里选择(LCA)

先声明一下(LCA)中常用的数组含义


(d[a])表示节点(a)的深度

(siz[a])表示节点(a)的子树大小(包括他自己)

(f[a][i])表示(a)(2^i)父亲


我们考虑在什么情况下会存在与(a,b)相同距离的点。设这个点为(c)

可以发现,当且仅当(a)(c) 的距离等于(b)(c)的距离,c存在

并且,我们可以发现,(c) 必定在(a)(lca)的路径上或 (b)(lca) 的路径上

综上,我们可以得到结论:当且仅当 (a)(b) 的路径长度为偶数时,(c) 存在

(a)(b) 的路径长度 (dis=d[a]+d[b]-d[lca] imes 2,dis\%2=0)(至于为什么,自己想想)


如果满足条件

我们对于每一个询问的(a,b),考虑情况

(1.) (a=b) ,显而易见答案为树的节点个数(n)

给个总图:

在这里插入图片描述

(2.) (a eq b)(d[a]=d[b]) , 即在图中 (a=5,b=7)

这样 (c) 必定为·(lca)

可以发现,他们在 (2) 节点相遇,之后的 (1) 节点的另外两棵子树上的点以及 (14) 节点都满足条件

于是, 得到 (ans=n-siz[5]-siz[7])

(3.) (a eq b)(d[a]<d[b]) ,即在图中 (a=10,b=4)

这样 (c)(3) 节点,之后的 (13) 节点满足条件

于是,得到 (ans=siz[3]-siz[8])

(4.) (a eq b)(d[a]>d[b])

同上


考虑完情况后,我们又遇到一个问题:如何在 (O(logn)) 时间内找到 (a,b) 相遇的节点,并找到应该减去子树大小的节点

我们还是选择倍增查找

假设 (d[a]<d[b])

(c) 必定在 (b)(lca) 的路径上

我们可以轻易得到 (c)(b) 的距离即 (b) 要向上跳的层数为(dis/2(dis\%2=0))

向上跳 (dis) 层,跟 (LCA) 的实现相似,就可以实现了

详细见代码:


代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n;
int to[N<<1];
int nxt[N<<1];
int head[N];
int cnt=0;
void add(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}
int f[N][25];
int d[N];
int siz[N];
void dfs(int u,int fa)
{
	siz[u]=1;
	f[u][0]=fa;
	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v!=fa)
		{
			d[v]=d[u]+1;
			dfs(v,u);
			siz[u]+=siz[v];
		}
	}
}
int get(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y])
        {
            x=f[x][i];
        }
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i],y=f[y][i];
        }
    }
    return f[x][0];
}
//以上是LCA模板
int id=0x7fffffff;
int work(int &son,int a,int deep)//找到相遇的节点以及要减去子树大小的节点son,deep表示向上跳deep层
{
	int dep=deep-1;
	for(int i=20;i>=0;--i)
	{
		if(dep>(1<<i))
		{
			dep-=(1<<i);
			a=f[a][i];
		}
	}
	son=a;
	return f[a][0];
}
int main()
{
	scanf("%d",&n);
	int a,b;
	int tot=0;
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&a,&b);
		add(a,b);add(b,a);
	}
	d[1]=1;
	int m;
	dfs(1,0);
	scanf("%d",&m);
	while(m--)
	{
		scanf("%d %d",&a,&b);
		if(a==b)
		{
			printf("%d
",n);
			continue;
		}
		int lca=get(a,b);
		int dis=d[a]+d[b]-d[lca]*2;
		if((dis%2)==1)puts("0");
		else 
		{
			dis/=2;
			int sona,sonb,v;
			if(d[a]==d[b])
			{
				work(sona,a,dis);
				work(sonb,b,dis);
				printf("%d
",n-siz[sona]-siz[sonb]);
			}
			else 
			{
				if(d[a]<d[b])
				{
					v=work(sonb,b,dis);
				    printf("%d
",siz[v]-siz[sonb]);
				}
				else 
				{
					v=work(sona,a,dis);
				    printf("%d
",siz[v]-siz[sona]);
					
				}
			}
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ShuraEye/p/11412931.html