cqyz oj | 树的相交路径 | 最近公共祖先

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;
}
posted @ 2019-08-27 21:39  Deguassing-compass  阅读(260)  评论(0编辑  收藏  举报