P1035 双径会深庭 官方题解
Gioush OJ · P1035 双径会深庭
双径会深庭
【题意简述】
给定一棵 个结点的树。每次询问给出 ,判断路径 与路径 是否至少经过同一个结点。
【Hint】
【提示】
设两条路径的最近公共祖先分别为 。若两条路径相交,则 位于第二条路径上,或 位于第一条路径上。
【数据点 1∼51\sim 51∼5】
对每次询问分别从 与 出发搜索,恢复两条简单路径,并标记路径上的全部结点。扫描全部结点,只要存在一个结点同时属于两条路径,答案就是 Yes。
每次询问需要两次搜索和一次扫描,时间复杂度为 ,空间复杂度为 。
【数据点 6∼106\sim 106∼10】
特殊性质保证整棵树是一条链。找到度数为 的端点后,沿链确定每个结点的位置。路径 便对应位置轴上的闭区间
两条路径相交当且仅当两个闭区间相交。预处理需要 的时间,每次询问为 。
这一档说明,我们不必恢复路径本身,只需要能够用少量端点信息判断一个结点是否位于路径上。
【数据点 11∼1511\sim 1511∼15】
这一档只有 ,仍然可以使用第一档的路径标记,时间复杂度为 。
去掉对 的限制后,需要支持两件事:求一条路径中深度最小的结点,以及判断一个结点是否位于给定路径上。这两件事都可以通过最近公共祖先完成。
【正解】
任选结点 为根,倍增预处理 。树上距离为
结点 位于路径 上,当且仅当
令
两条路径相交当且仅当 位于路径 上,或者 位于路径 上。
【正确性证明】
若 位于第二条路径上,则 同时位于第一条路径与第二条路径,两条路径相交。 的情况同理,因此判定的充分性成立。
接下来证明必要性。假设两条路径相交,令 为交集中深度最小的结点。由于 分别位于两条路径上,所以 都是 的祖先。树上同一结点的祖先构成一条链,因此 必然具有祖先关系。
不妨设 的深度不小于 。那么 位于从 到 的祖先链上。由于 都位于第二条路径上,二者之间的整段路径也属于第二条路径,所以 位于第二条路径上。另一种深度关系同理,必要性成立。
综上所述,该判定是充要的。
【复杂度分析】
倍增预处理的时间复杂度为 。每次询问只进行常数次最近公共祖先与距离计算,时间复杂度为 。总时间复杂度为 ,空间复杂度为 。
【参考代码】
/*- @Author: Ehundategh- @Date: 2023-11-07 15:16:31- @FilePath: \Code\11.6\仓鼠找Sugar.cpp- @Description: You Steal,I Kill */#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 100010using namespace std;int Fa[MAXN][21],Total,Head[MAXN],Depth[MAXN],In1,In2,In3,In4;struct edge{;;;;int St,Ed,Next;}Edge[MAXN<<1];void Edge_Add(int St,int Ed){;;;;Edge[++Total]={St,Ed,Head[St]};;;;;Head[St]=Total;}void DFS(int Now,int From){;;;;Fa[Now][0]=From;;;;;Depth[Now]=Depth[From]+1;;;;;for(int i=Head[Now];i;i=Edge[i].Next){;;;;;;;;int To=Edge[i].Ed;;;;;;;;;if(To==From) continue;;;;;;;;;DFS(To,Now);;;;;}}int LCA(int a,int b){;;;;if(Depth[a]<Depth[b]) swap(a,b);;;;;for(int i=19;i>=0;i--){;;;;;;;;if(Depth[Fa[a][i]]>=Depth[b]) a=Fa[a][i];;;;;};;;;if(a==b) return a;;;;;for(int i=19;i>=0;i--){;;;;;;;;if(Fa[a][i]!=Fa[b][i]){;;;;;;;;;;;;a=Fa[a][i];;;;;;;;;;;;;b=Fa[b][i];;;;;;;;;};;;;};;;;return Fa[a][0];}int Get(int a,int b){;;;;int lca=LCA(a,b);;;;;return Depth[a]+Depth[b]-2*Depth[lca];}bool Query(int a,int b,int c,int d){;;;;int Lca1=LCA(a,b);;;;;int Lca2=LCA(c,d);;;;;int Dis1=Get(a,b);;;;;int Dis2=Get(c,d);;;;;int Judge1=Get(Lca1,c)+Get(Lca1,d);;;;;int Judge2=Get(Lca2,a)+Get(Lca2,b);;;;;if(Judge1==Dis2||Judge2==Dis1) return true;// ;;;;if(LCA(Lca1,Lca2)==Lca2&&(LCA(Lca1,d)==Lca1||LCA(Lca1,c)==Lca1)) return true;// ;;;;if(LCA(Lca1,Lca2)==Lca1&&(LCA(Lca2,a)==Lca2||LCA(Lca2,b)==Lca2)) return true;;;;;return false;}void Solve(){;;;;int n,q;;;;;scanf("%d%d",&n,&q);;;;;Total=0;;;;;memset(Head,0,sizeof(int)*(n+1));;;;;memset(Depth,0,sizeof(int)*(n+1));;;;;memset(Fa,0,sizeof(int)*(n+1)*21);;;;;for(int i=1;i<n;i++){;;;;;;;;scanf("%d%d",&In1,&In2);;;;;;;;;Edge_Add(In1,In2);;;;;;;;;Edge_Add(In2,In1);;;;;};;;;DFS(1,1);;;;;for(int i=1;i<=19;i++){;;;;;;;;for(int j=1;j<=n;j++){;;;;;;;;;;;;Fa[j][i]=Fa[Fa[j][i-1]][i-1];;;;;;;;;};;;;};;;;while(q-->0){;;;;;;;;scanf("%d%d%d%d",&In1,&In2,&In3,&In4);;;;;;;;;printf("%s\n",Query(In1,In2,In3,In4)?"Yes":"No");;;;;}}int main(){;;;;int c,T;;;;;scanf("%d%d",&c,&T);;;;;while(T-->0) Solve();;;;;return 0;}