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

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

时间: 2024-10-13 21:42:00

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

cqyz oj | 树的分治 | 树形DP | 树的重心

Description 给定一棵N个节点的带权树,定义dist(u,v)为u,v两点间的最短路径长度,路径的长度义为路径上所有边的权和.再给定一个K,如果对于不同的两个结点a,b,如果满足dist(a,b)<=K,则称(a,b)为合法点对. 你的任务是求合法点对个数. Input 第一行包含两个个整数N和K,接下来的N-1行,每行包含三个整数:u,v,len,表示树边(u,v)的长度len. Output 一个整数,表示合法点对的数目. Sample Input 1 5 4 1 2 3 1 3

树链剖分 [模板]最近公共祖先LCA

本人水平有限,题解不到为处,请多多谅解 本蒟蒻谢谢大家观看 题目:传送门 树链剖分:跑两遍dfs,第一遍找重边,第二遍找重链. 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点: 轻儿子:父亲节点中除了重儿子以外的儿子: 重边:父亲结点和重儿子连成的边: 轻边:父亲节点和轻儿子连成的边: 重链:由多条重边连接而成的路径: 轻链:由多条轻边连接而成的路径 son[]表示重儿子,top[]表示重链所在的第一个节点,sz[]表示子节点数,fa[]表示父亲节点 图示: code: #i

51 nod 1681 公共祖先 (主席树+dfs序)

1681 公共祖先 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 有一个庞大的家族,共n人.已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边). 在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈…… 两个人的亲密度定义为在这两个平行宇宙有多少人一直是他们的公共祖先. 整个家族的亲密度定义为任意两个人亲密度的总和. Input 第一行一个数n(1<=n<=100000)

50、树中两个节点的公共祖先

详细的询问: 1.该树是二叉查找树? 最近公共祖先----二叉查找树:(http://www.lintcode.com/problem/lowest-common-ancestor/) 思路:利用左子树特点:左子树 < 根 <= 右,输入节点跟根节点比较,都小于,在左子树,都大约右子树,递归的去遍历:找到当前节点在两个输入大小之间,当前节点就是. 递归和非递归 public class Solution { public TreeNode lowestCommonAncestor(TreeNo

BZOJ 1977 次小生成树(最近公共祖先)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1977 题意:求一棵树的严格次小生成树,即权值严格大于最小生成树且权值最小的生成树. 思路:若现在已经得到了最小生成树,那么若 添加一条边E,就会得到一个环,我们只需要去掉环上权值小于E且最大的一条边就会得到另一棵较优的生成树.因此,只需要枚举不在生成树上的边,计算将其添 加到最小生成树中得到的新生成树的权值.取最小值即可.那么,现在的问题就是在一个圈中找到一个最大的小于新添加的边的权值

cqyz oj | 树上的询问 | 最近公共祖先

Description 现有一棵 n 个节点的棵, 树上每条边的长度均为 1. 给出 m 个询问, 每次询问两个节点 x,y, 求树上到 x,y 两个点距离相同的节点数量. Input 第一个整数 n, 表示树有 n 个点. 接下来 n-1 行每行两整数 a, b, 表示从 a 到 b 有一条边. 接下来一行一个整数 m, 表示有 m 个询问. 接下来 m 行每行两整数 x, y, 询问到 x 和 y 距离相同的点的数量. Output 共 m 行, 每行一个整数表示询问的答案. Sample

二棵树某两个节点的公共祖先。

1. 如果是有parent指针的树,可以转化成 求两个链表第一个公共节点的问题. 对于无parent指针普通二叉树(假定这两个节点一定在树中,否则需要先遍历一边树查找是否存在该节点) 1. (剑指offer的解法),先用一定的空间记录从根节点到两个节点各自的路径,然后找这两个路径最后一个相交的节点. 2.  CC150,递归求解. TreeNode commonAncestor(TreeNode root, TreeNode p, TreeNode q){ } 二棵树某两个节点的公共祖先.,布布

LeetCode OJ:Lowest Common Ancestor of a Binary Tree(最近公共祖先)

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w

Codeforces 618D Hamiltonian Spanning Tree(树的最小路径覆盖)

题意:给出一张完全图,所有的边的边权都是 y,现在给出图的一个生成树,将生成树上的边的边权改为 x,求一条距离最短的哈密顿路径. 先考虑x>=y的情况,那么应该尽量不走生成树上的边,如果生成树上有一个点的度数是n-1,那么必然需要走一条生成树上的边,此时答案为x+y*(n-2). 否则可以不走生成树上的边,则答案为y*(n-1). 再考虑x<y的情况,那么应该尽量走生成树上的边,由于树上没有环,于是我们每一次需要走树的一条路,然后需要从非生成树上的边跳到树的另一个点上去, 显然跳的越少越好,于