题意简述
给定一棵无根树,要求回答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; }