题目
题意
判定树上的两条路径是否有公共点。
算法
lca
分析
假设两条路径有公共点,那么公共点中必定包括路径1的某一端点和路径2的某一端点的最近公共祖先
但至于是哪一对就不得而知了
那为什么是这样呢
反证法
若两条路径都不经过任意一对点的最近公共祖先
如下图
在最近公共祖先的两棵子树上,老死不相往来。
有了这个基础,
我们要如何判定两条路径都经过了lca呢
现在介绍一个船新的方法–判定一个点是否在一条路径上
设dis(i,j)为i,j两点的最短距离
设u,v为路径的两端点。
设k为判定点
则满足dis(k,u)+dis(k,v)==dis(u,v)
k点在以u,v为两端点的路径上。
证明可用反证法
接下来上代码
#include<bits/stdc++.h>
using namespace std;
const int ll=3e5;
int n,m,tot,t;
int head[ll],next[ll],ver[ll];
int d[ll],f[ll][20],fa[ll],dis[ll];
void add(int u,int v){
tot++;
ver[tot]=v;
next[tot]=head[u];
head[u]=tot;
}
int lca(int x,int y){
if(d[x]>d[y]) swap(x,y);
for(int i=t;i>=0;i--)
if(d[f[y][i]]>=d[x])
y=f[y][i];
if(x==y) return x;
for(int i=t;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
bool pd(int a,int b,int c,int len){
return 2*dis[a]+dis[b]+dis[c]-2*(dis[lca(a,b)]+dis[lca(a,c)])==len;
}
int main(){
scanf("%d%d",&n,&m);
t=log(n)/log(2)+1;
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
queue <int> q;
q.push(1);
d[1]=1;
while(q.size()){
int x=q.front(); q.pop();
for(int i=head[x]; i ;i=next[i]){
int v=ver[i];
if(d[v]) continue;
d[v]=d[x]+1;
f[v][0]=x;
dis[v]=dis[x]+1;
fa[v]=x;
for(int j=1;j<=t;j++)
f[v][j]=f[f[v][j-1]][j-1];
q.push(v);
}
}
for(int i=1;i<=m;i++){
int a,b,c,end;
scanf("%d%d%d%d",&a,&b,&c,&end);
if(d[a]<d[b]) swap(a,b);
if(d[c]<d[end]) swap(c,end);
int len1=dis[a]+dis[b]-2*dis[lca(a,b)];
int len2=dis[c]+dis[end]-2*dis[lca(c,end)];
int father=lca(a,c);
if(pd(father,a,b,len1)&&pd(father,c,end,len2)){
printf("Y\n");continue;}
father=lca(a,end);
if(pd(father,a,b,len1)&&pd(father,c,end,len2)){
printf("Y\n");continue;}
father=lca(b,end);
if(pd(father,a,b,len1)&&pd(father,c,end,len2)){
printf("Y\n");continue;}
father=lca(b,c);
if(pd(father,a,b,len1)&&pd(father,c,end,len2)){
printf("Y\n");continue;}
printf("N\n");
}
}