【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]\times 2,dis\%2=0\)(至于为什么,自己想想)


如果满足条件

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

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

给个总图:

在这里插入图片描述

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

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

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

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

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

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

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

\(4.\) \(a\neq 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",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",n-siz[sona]-siz[sonb]);
			}
			else 
			{
				if(d[a]<d[b])
				{
					v=work(sonb,b,dis);
				    printf("%d\n",siz[v]-siz[sonb]);
				}
				else 
				{
					v=work(sona,a,dis);
				    printf("%d\n",siz[v]-siz[sona]);
					
				}
			}
		}
	}
	return 0;
}
posted @ 2019-08-26 15:42  ShuraEye  阅读(218)  评论(0编辑  收藏  举报