码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2019-08-27 22:48:50      阅读:123      评论:0      收藏:0      [点我收藏+]

标签:格式   while   code   类型   个推   name   oid   include   math   

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;
}

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

标签:格式   while   code   类型   个推   name   oid   include   math   

原文地址:https://www.cnblogs.com/de-compass/p/11421021.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!