Description
给定含 n 个结点、边带权的无根树,请回答下面的询问:
1 a b c d:询问路径a->b是否是路径c->d的子路径。
2 a b c d:询问路径a->b和c->d的最长公共路径长度。
Input
第一行包括两个正整数 n,m,表示树的节点数和询问数,树结点从1到n编号。
接下来n-1行描述树边情况,其中第i行包含三个整数 a, b和t,表示第i条双向连接a和b,长度为t。
接下来m行描述询问情况,每一个询问如题目描述格式。
Output
每个询问的回答输出一行,如果询问类型是1,则输出Yes或No,如果是询问类型2,则输出公共路径长度,如果没有公共路径,则输出0。
Sample Input 1
11 4
1 6 3
2 1 2
4 3 1
6 7 4
9 8 2
3 1 2
3 5 6
2 10 3
10 11 2
8 6 1
1 3 6 4 9
1 5 7 6 2
2 8 10 7 3
2 9 11 10 1
Sample Output 1
Yes
No
3
5
Hint
1<n,m<=300 000,
1<=t<=1000.
1、询问 1:
如果路径 a->b 在路径 c->d 上,必然满足下面的条件之一:
len(a,c)+len(a,b)+len(b,d)=len(c,d)
或者:len(a,d)+len(a,b)+len(b,c)=len(c,d)
这里的 len(u,v)=dist(u)++dist(v)-2*dist(LCA(u,v)),即路径 u->v 的长度。
这个问题的证明很简单:
假设 a 或 b 不在 c->d 的路径上(假设 a 一定不在),但满足上面的某个条件(假设满足条件 1),
那么有:len(c,a)+len(a,b)>len(c,b)即 len(c,a)+len(a,b)+len(b,d)>len(c,d)矛盾!
2、询问 2:
推论:设 x,y 是 a->b 与 c->d 有公共路径的两个端点,则 x,y 一定是下面 6 个点中不同的两个:
p[1]=LCA(a,b);
p[2]=LCA(a,c);
p[3]=LCA(a,d);
p[4]=LCA(b,c);
p[5]=LCA(b,d);
p[6]=LCA(c,d);
对这个推论的证明很简单:
两条路径的公共点一定是他们端点的祖先,而最长公共部分一定是他们端点的最近的公共祖先
3、时间复杂度分析
询问 1 和询问 2 的复杂度主要耗费在 LCA 算法上,所以算法时间复杂度为?(? × ???2 ?),常数比较
大,特别是询问 2,常数为 6*6/2=18。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 300005
#define maxm 300005
using namespace std;
int fir[maxn], ne[maxm], to[maxm], w[maxm], np=0;
void add(int x,int y,int z){
ne[++np] = fir[x];
fir[x] = np;
to[np] = y;
w[np] = z;
}
int dist[maxn], dep[maxn], fa[maxn][20], mx[maxn][20];
void dfs(int u,int f,int d,int t){
dist[u] = t;
dep[u] = d;
fa[u][0] = f;
mx[u][0] = dist[u] - dist[f];
for(int k = 1; k <= 18; k++){
int j = fa[u][k-1];
fa[u][k] = fa[j][k-1];
mx[u][k] = max( mx[u][k-1], mx[j][k-1]);
}
for(int i = fir[u]; i; i=ne[i]){
int v = to[i];
if(v == f)continue;
dfs(v, u, d+1, t+w[i]);
}
}
int LCA(int x,int y){
if(dep[x] < dep[y]) swap(x, y);
int j = dep[x] - dep[y];
for(int k = 18; k >= 0; k--)
if((1<<k)&j) x = fa[x][k];
if(x == y) return x;
for(int k = 18; k >= 0; k--)
if(fa[x][k] != fa[y][k])
x = fa[x][k], y = fa[y][k];
return fa[x][0];
}
int len(int u,int v){ return dist[u] + dist[v] - 2*dist[LCA(u, v)];}
int n, m;
void data_in(){
scanf("%d%d", &n, &m);
for(int i = 1, x, y, z; i < n; i++){
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);add(y, x, z);
}
}
void task1(int a,int b,int c,int d){
int ab = len(a,b), cd = len(c, d);
if(len(c,a)+ab+len(b,d) == cd || len(c,b)+ab+len(a,d) == cd) puts("Yes");
else puts("No");
}
void task2(int a,int b,int c,int d){
int p[10], tot=0;
int ab = dist[a] + dist[b] - 2*dist[p[tot++] = LCA(a,b)];
p[tot++] = LCA(a,c);
p[tot++] = LCA(a,d);
p[tot++] = LCA(b,c);
p[tot++] = LCA(b,d);
int cd = dist[c] + dist[d] - 2*dist[p[tot++] = LCA(c,d)];
int mx = 0;
sort(p, p+tot);
tot = unique(p, p+tot) - p;
for(int i=0;i<tot;i++)
for(int j=i;j<tot;j++){
int x = p[i], y = p[j], xy = len(x,y);
if(len(a,x)+xy+len(y,b) == ab || len(b,x)+xy+len(y,a) == ab)
if(len(c,x)+xy+len(y,d) == cd || len(d,x)+xy+len(y,c) == cd)
mx = max(mx, xy);
}
printf("%d\n",mx);
}
void solve(){
dfs(1, 0, 0, 0);
int op, a, b, c, d;
while(m--){
scanf("%d%d%d%d%d", &op, &a, &b, &c, &d);
if(op == 1) task1(a, b, c, d);
else task2(a, b, c, d);
}
}
int main(){
data_in();
solve();
return 0;
}