dfs序+RMQ求LCA详解

首先安利自己倍增求LCA的博客,前置(算不上)知识在此。

LCA有3种求法:倍增求lca(上面qwq),树链剖分求lca(什么时候会了树链剖分再说。),还有,标题。

是的你也来和我一起学习这个了qwq。

开始吧。

众所周知,每当你dfs时,你都能产生一棵dfs树,可以根据你的dfs序来构建。

such as(丑陋的画风):

一个dfs的顺序。

以这个为例:

那么我们写出他的遍历顺序:

假如我们要求3,8(wtf?)的LCA,

那么我们首先写出他的bfs序:

123432565217871。

然后留意一下我们要求的两个数的位置。

123432565217871。

我们发现这样一个事情

两个数的LCA,一定在前一个数最后一次出现的位置(在bfs序中)。

感性证明

对于前一个数最后一次出现的位置,他的意义就是当前节点的子树已经遍历完了,并且正在进行回溯!(拍桌,划重点!)。

也就是说,他要回溯到他的祖先了,而它的祖先同样也是后一个节点的祖先,一定在后一个节点遍历前,前一个节点回溯后。

前一个节点<lca<后一个节点。

证毕。

那么,我们只要找到dfs遍历顺序中的 “前一个数最后一次出现的位置,后一个数第一次出现的位置”,这个区间取出区间最小值,即是两个节点的lca。

或许有人会说:为什么最小值一定是lca呢?

又需要证明了。

我们从几何学的角度来解释:

由图可知,两个节点分别在LCA的两个不同的子树中。

当A节点最后一次遍历完,经过一系列回溯,一定能回溯的LCA。

但是因为LCA的子树没有遍历完(链式存图i=edge[i].to),所以它只会遍历到LCA,然后继续遍历lca的子树直到遍历到B点。

感性证毕。

内么,区间最小值且不带修改的我们很容易想到st表

所以,dfs序+RMQ求LCA成立。

复杂度nlogn查询+o1查询,三者最优。

代码:

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5 + 10 ;
vector<int> g[M] ;
int n ;

vector<int> vs ;//dfs order
int tot ;
int orm[M] ;
int id[M] ;
int dep[M] ;

int d[M][30] ;//RMQ
void dfs (int o , int u ,int  DEP) {
        int tmp = tot ++ ;
        dep[u] = DEP ;
        id[u] = vs.size () ;
        orm[tmp] = u ;
        vs.push_back (tmp) ;

        for (int i = 0 ; i < g[u].size () ; i ++) {
                int v = g[u][i] ;
                if (v == o) continue ;
                dfs (u , v , DEP + 1) ;
        }
        int len = vs.size () ;
        if (vs[len-1] == tmp) vs.push_back (vs[id[o]]) ;
        else vs.push_back (tmp) ;
}

void init_RMQ () {
        for (int i = 0 ; i < 2*n-1 ; i ++) d[i][0] = vs[i] ;
        for (int j = 1 ; (1 << j) <= n ; j ++) {
                for (int i = 0 ; i + (1 << j) <= n ; i ++) {
                        d[i][j] = min (d[i][j-1] , d[i+(1<<(j-1))][j-1]) ;
                }
        }
}

int RMQ (int l , int r) {
        printf ("l = %d , r = %d\n" , l , r ) ;
        int k = 0 ;
        while ( (1<<(k+1)) <= r - l + 1) k ++ ;
        int tmp = min (d[l][k] , d[1+r-(1<<k)][k]) ;
        return orm[tmp] ;
}
void Print () {
        for (int i = 0 ; i < 2*n-1 ; i ++) printf ("%3d " , i ) ; puts ("") ;
        puts ("dfs order:") ;
        for (int i = 0 ; i < 2*n-1 ; i ++) printf ("%3d " , vs[i]) ; puts ("") ;
        puts ("deep:") ;
        for (int i = 0 ; i < n ; i ++) printf ("%3d " , dep[i]) ; puts ("") ;
        puts ("id :") ;
        for (int i = 0 ; i < n ; i ++) printf ("%3d " , id[i]) ; puts ("") ;
}

void LCA () {
        dfs (0,0,0) ;
        init_RMQ () ;
        Print () ;
}

int main () {
        cin >> n ;
        for (int i = 0 ; i < n - 1 ; i ++) {
               int u , v ;
               cin >> u >> v ;
               g[u].push_back (v) ;
               g[v].push_back (u) ;
        }
        LCA () ;
        int Q ;
        cin >> Q ;
        while (Q --) {
                int u , v ;
                cin >> u >> v ;
                if (id[u] > id[v]) swap (u , v ) ;
                int ans = RMQ (id[u] , id[v]) ;
                printf ("The %d and %d the lastest ans is %d , and they are away from %d\n" , u , v , ans , dep[u]+dep[v]-2*dep[ans]) ;
        }
        return 0 ;
}

——lyfdalao

完结。

原文地址:https://www.cnblogs.com/lbssxz/p/11332818.html

时间: 2024-11-01 20:33:24

dfs序+RMQ求LCA详解的相关文章

6.跟我学solr---请求参数详解

简介 前面我们在讲SolrRequestHandler和QueryResponseWriter的时候提到过两个参数'qt'和'wt",这两个参数是分别用于选择对应的SolrRequestHandler和QueryResponseWriter的.solr定义了很多类似的参数,它们都分别属于某个大类中,例如"qt"和"wt"就属于CoreQueryParameters.下面罗列一下solr的所有参数列表,来源于solr官网.下面笔者会一一给大家讲解这些参数的作

【RMQ】洛谷P3379 RMQ求LCA

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 复制 5 5 4 3 1 2 4

【树链剖分】【dfs序】【LCA】【分类讨论】Codeforces Round #425 (Div. 2) D. Misha, Grisha and Underground

一棵树,q次询问,每次给你三个点a b c,让你把它们选做s f t,问你把s到f +1后,询问f到t的和,然后可能的最大值是多少. 最无脑的想法是链剖线段树--但是会TLE. LCT一样无脑,但是少一个log,可以过. 正解是分类讨论, 如果t不在lca(s,f)的子树内,答案是dis(lca(s,f),f). 如果t在lca(s,f)的子树内,并且dep(lca(s,t))>dep(lca(f,t)),答案是dis(lca(s,t),f): 否则答案是dis(lca(f,t),f). #in

POJ 1330 Nearest Common Ancestors (最近公共祖先LCA + 详解博客)

LCA问题的tarjan解法模板 LCA问题 详细 1.二叉搜索树上找两个节点LCA 1 public int query(Node t, Node u, Node v) { 2 int left = u.value; 3 int right = v.value; 4 5 //二叉查找树内,如果左结点大于右结点,不对,交换 6 if (left > right) { 7 int temp = left; 8 left = right; 9 right = temp; 10 } 11 12 whi

HDU 1010 Tempter of the Bone【DFS经典题+奇偶剪枝详解】

Tempter of the Bone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 125945    Accepted Submission(s): 33969 Problem Description The doggie found a bone in an ancient maze, which fascinated him a

二叉树的先序遍历(递归方式)详解

/* * 时间:2015年5月4日09:58:21 * 目的:实现二叉树的先序遍历递归. * 总结和思考!二叉树一般使用链式存储结构 * 如果使用顺序存储方式,除非二叉树是完全二叉树或者满二叉树 * 否则会浪费很多内存空间! */ # include <stdio.h> typedef struct Node{ char data; //数据元素 Node *LChild; //左孩子节点 Node *RChild; //右孩子节点 }BTree; /*递归算法--先序遍历*/ //二叉树的创

树状数组的改段求段详解

以下是对于如何利用树状数组进行区间修改和区间查询的简介 可以代替不需要lazy tag的线段树,且代码量和常数较小 首先你需要学会树状数组,如果不会的话以下先讲解黑匣子使用树状数组的姿势 首先定义一个数组 int c[N]; 并清空 memset(c, 0, sizeof c); 1.单点修改 : c[x] += y; 对应的函数是 change(x, y); 2.求前缀和 :  对应的函数是 int sum(x) 两种操作的复杂度都是O(logn) 模版如下 int c[N], maxn; i

【learning】 多项式求逆元详解+模板

概述 多项式求逆元是一个非常重要的知识点,许多多项式操作都需要用到该算法,包括多项式取模,除法,开跟,求ln,求exp,快速幂.用快速傅里叶变换和倍增法可以在$O(n log n)$的时间复杂度下求出一个$n$次多项式的逆元. 前置技能 快速数论变换(NTT),求一个数$x$在模$p$意义下的乘法逆元. 多项式的逆元 给定一个多项式$A$,其次数为$deg_A$,若存在一个多项式$B$,使其满足$deg_B≤deg_A$,且$A(x)\times B(x) \equiv 1 (mod x^n)$

如何实现一个高效的单向链表逆序输出?(详解)

需要考虑因素,高效应权衡多方面因素 数据量是否会很大 空间是否有限制 原始链表的结构是否可以更改 时间复杂度是否有限制 一个链表节点需要输出的元素有多个,例如链表中存的是自定义对象,有多个字段 题目. 01. 先学着实现一个简单的Java版的单项链表构建任意长度的任意数值的链表, 头插法,顺序遍历输出链表 package com.szs.list; /** * 单链表 * @author Administrator * */ public class MyLinkedList { public