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 Input 1 

7
1 2
1 3
2 4
2 5
3 6
3 7
3
1 2
4 5
2 3

Sample Output 1

0
5
1

Hint

对于 30%的数据, 满足 n≤50, m≤50

对于 60%的数据, 满足 n≤1000, m≤1000

对于 100%的数据, 满足 n≤100000, m≤100000


思路:如果存在点到x,y距离相等,这个点一定是中点,或在中点的其他子树上

写法思路:

  首先特判if(x==y) ans=n

  然后用最近公共祖先LCA算法计算两点间的距离dis(即路径上的树边数),如果dis是奇数则中点不在节点上,ans=0

  dis为偶数则可以找到中点。从两点中深度较大的那个点(设这个点是x)向上爬dis/2个距离找到中点mid。

  容易想到ans=n-size[x所在子树]-size[y所在子树],但是受建树时根节点不同的影响,y可能不在mid的子树里,而在mid的父亲那条分支里

  这时候ans=size[mid]-size[x所在子树]

  用if(mid==lca(x,y))为真判定y在mid的子树里(想一想,为什么),之后就没什么细节了。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 100005
#define maxm 200005
#define id(x) ((x+1)>>1)
using namespace std;
int fir[maxn], ne[maxm], to[maxm], np;
void add(int x,int y){
    ne[++np] = fir[x];
    fir[x] = np;
    to[np] = y;
}

int dep[maxn], fa[maxn][20], siz[maxn];
void dfs(int u,int f,int d){
    dep[u] = d; siz[u] = 1;
    fa[u][0] = f;
    for(int k = 1; k <= 18; k++){
        int j = fa[u][k-1];
        fa[u][k] = fa[j][k-1];
    }
    for(int i = fir[u]; i; i=ne[i]){
        int v = to[i];
        if(v != f)
            dfs(v, u, d+1), siz[u] += siz[v];
    }
}

int jump(int u, int x) {
    for(int k = 18; k >= 0; k--)
        if((1<<k)&x) u = fa[u][k];
    return u;
}

int jump2(int u,int anc){
    for(int k = 18; k >= 0; --k)
        if(dep[fa[u][k]] > dep[anc]) u = fa[u][k];
    return u;
}

int LCA(int x,int y){
    x = jump(x, dep[x] - dep[y]);
    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 n, m;
void data_in() {
    memset(fir, 0, sizeof(fir)); np = 0;
    int u, v;
    scanf("%d", &n);
    for(int i = 1; i < n; ++i) {
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
}

void solve() {
    dfs(1, 0, 1);

    int u, v, mid, dis, lca;
    scanf("%d", &m);
    while(m--) {
        scanf("%d%d", &u, &v);
        if(u==v)printf("%d\n", n);
        else{
            if(dep[u] < dep[v]) swap(u, v);
            dis = dep[u] + dep[v] - 2*dep[lca = LCA(u, v)];
            if(dis%2 == 0) {
                mid = jump(u, dis/2);
                u = jump2(u, mid);
                if(mid == lca){
                    v = jump2(v, mid);
                    printf("%d\n", n - siz[u] - siz[v]);
                }
                else printf("%d\n", siz[mid] - siz[u]);
            }
            else printf("0\n");
        }

    }
}

int main(){
    data_in();
    solve();
    return 0;
}

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

时间: 2024-08-30 02:25:17

cqyz oj | 树上的询问 | 最近公共祖先的相关文章

【算法】树上公共祖先的Tarjan算法

最近公共祖先问题 树上两点的最近公共祖先问题(LCA - Least Common Ancestors) 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u和v的祖先且x的深度尽可能大.在这里,一个节点也可以是它自己的祖先. 例如,如图,在以A为根的树上 节点 8 和 9 的LCA为 4 节点 8 和 5 的LCA为 2 节点 8 和 3 的LCA为 1 在线算法与离线算法 区别就在于是同时处理询问后输出还是边询问边输出 在线算法是读入一组询问,查询一次后紧

我对最近公共祖先LCA(Tarjan算法)的理解

LCA 最近公共祖先 Tarjan(离线)算法的基本思路及我个人理解 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而

最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

最近公共祖先LCA(Tarjan算法)的思考和算法实现 LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了flase...看的时候注意一下! //还有...这篇字比较多 比较杂....毕竟是第一次嘛 将就将就 后面会重新改!!! 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先

[转]LCA 最近公共祖先

原文传送门orzJVxie Tarjan(离线)算法的基本思路及其算法实现 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖

LCA(最近公共祖先)——离线 Tarjan 算法

一.梳理概念 定义:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 通俗地讲,最近公共祖先节点,就是两个节点在这棵树上深度最大的公共的祖先节点,即两个点在这棵树上距离最近的公共祖先节点. 提示:父亲节点也是祖先节点,节点本身也是它的祖先节点. 给出一棵树,如图所示: 由上面的定义可知:3和5的最近公共祖先为1,5和6的最近公共祖先为2,2和7的最近公共祖先为2, 6和7的最近公共祖先为4. 二.繁文缛节 注意注意注意!!!尚

笔记:LCA最近公共祖先 Tarjan(离线)算法

LCA最近公共祖先 Tarjan他贱(离线)算法的基本思路及其算法实现 本文是网络资料整理或部分转载或部分原创,参考文章如下: https://www.cnblogs.com/JVxie/p/4854719.html http://blog.csdn.net/ywcpig/article/details/52336496 https://baike.baidu.com/item/最近公共祖先/8918834?fr=aladdin 最近公共祖先简称LCA(Lowest Common Ancesto

算法详解之最近公共祖先(LCA)

若图片出锅请转至here 概念 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而LCA还可以将自己视为祖先节点. 举个

【C++】最近公共祖先LCA(前置知识)

1.前言 最近公共祖先(Least Common Ancestors),简称LCA,是由Tarjan教授(对,又是他)提出的一种在有根树中,找出某两个结点u和v最近的公共祖先问题. 2.什么是最近公共祖先? 在一棵树中,每个结点都有他的父亲和祖先,而最近公共祖先就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点.结合下图和文字应该很好的诠释了最近公共祖先: PS:在LCA中,也可以将结点本身视为自己的祖先 在这颗以结点1为根的树中,4与5的最近

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 每个询问的回答