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