题意简述
给定一棵无根树,要求回答m次询问,每次询问给定两个点u,v,求树上与这两个点距离相等的点的个数。距离定义为树上两点间的边数。
算法概述
这道题难度评定成紫色着实有点过了,个人感觉封顶蓝色。毕竟前置知识只有一个树上倍增,而且也没什么思维难度,就简单分类讨论一下就完事了。
手动画一画样例不难发现,能够作为答案的点中,距离给定两点u,v最近的点,肯定在路径u→v上,且必然为路径的中点。对这个点分两种情况考虑:
1. 这个点就是lca(u,v),则说明u,v深度相同。但u,v深度相同并不能直接说明这个点就是lca(u,v),因为还有一种特殊情况即u,v为同一点,特判即可,这种情况的答案显然是树上所有点都能走。然后对于这个点为lca(u,v)的情况,我们执行树上倍增的部分过程,让u,v同时上跳,跳到lca(u,v)的儿子x,y为止,那么答案即为n-size[x]-size[y],因为这个点可以走到除了这两棵子树以外的所有点,这些点都能成为答案。
2. 这个点不是lca(u,v),那么u,v深度一定不同,不妨设u为深度大的点。假设路径中点为x,则v必然要经过lca(u,v)再向下走,才能走到x,而u只需上跳即可,设u跳到x的路上,经过的x的儿子节点为y。那么答案则需要将树上的这两部分剪除,即为size[x]-size[y]。跳的过程同样用倍增即可。
时间复杂度O(mlogn)。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10,MAX_LOG=17;
struct Edge{
int to,next;
}edge[N<<1];int idx;
int h[N];
void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
int f[N][MAX_LOG];
int dep[N],siz[N];
int n,m;
void dfs(int p,int fa)
{
dep[p]=dep[fa]+1;
siz[p]=1;
for(int i=h[p];~i;i=edge[i].next)
{
int to=edge[i].to;
if(to==fa)continue;
f[to][0]=p;
for(int j=1;j<MAX_LOG;j++)
f[to][j]=f[f[to][j-1]][j-1];
dfs(to,p);
siz[p]+=siz[to];
}
}
inline int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int j=MAX_LOG-1;j>=0;j--)
if(d>=(1<<j))d-=(1<<j),x=f[x][j];
if(x==y)return x;
for(int j=MAX_LOG-1;j>=0;j--)
if(f[x][j]!=f[y][j])x=f[x][j],y=f[y][j];
return f[x][0];
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
for(int i=1;i<=n-1;i++)
{
int u,v;scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
scanf("%d",&m);
while(m--)
{
int u,v;scanf("%d%d",&u,&v);
if(dep[u]==dep[v])
{
if(u==v){printf("%d
",n);continue;}
for(int j=MAX_LOG-1;j>=0;j--)
if(f[u][j]!=f[v][j])u=f[u][j],v=f[v][j];
printf("%d
",n-siz[u]-siz[v]);
}
else
{
int d=dep[u]+dep[v]-2*dep[lca(u,v)];
if(d%2){printf("0
");continue;}
d/=2;d--;
if(dep[u]<dep[v])swap(u,v);
for(int j=MAX_LOG-1;j>=0;j--)
if(d>=(1<<j))d-=(1<<j),u=f[u][j];
int x=f[u][0];
printf("%d
",siz[x]-siz[u]);
}
}
return 0;
}