【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;
}