首先,众所周知,求LCA共有3种算法(树剖就不说了,太高级,以后再学。。)。

1、树上倍增(ST表优化)

2、RMQ&时间戳(ST表优化)

3、tarjan(离线算法)不讲。。(后面补坑啦!)

一、树上倍增

这种方法原理是这样的:

我们可以知道,最朴素的算法就是一步一步的并查集往上找,知道找到2个点并查集相同,即为LCA

但这种算法的时间效率为O(NM)看到0<n,m<5*10^5我们就知道一定会炸。

但是,我们可以发现给出树后,每个点的LCA及走到LCA的路径一定是固定的。

所以可以ST表优化。

首先先BFS出每个点在树上的深度。。(记为depth[i])

接着我们要先让2个点的深度相同,之后2个点一起走,可以加快效率。

最后我们直接倍增上去。(if(fa[i][u]!=fa[i][v])u=fa[i][u];v=fa[i][v];)

最后只要往上走一步,就是LCA了。

重点!倍增时倍增循环在外!不能在内!否则会挂!

下面贴代码(debug了三小时,才调好。膜拜zxyer)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct data{
int next,to;
}g[];
int depth[];
int que[];
bool visit[];
int head[];
int fa[][];
int n,q,root,num=;
int lca(int u,int v){
if(depth[u]<depth[v])swap(u,v);
int dc=depth[u]-depth[v];
for(int i=;i<=;i++)
if((<<i)&dc&&fa[i][u]){
u=fa[i][u];
}
if(u==v)return u;
for(int i=;i>=;i--)
if(fa[i][u]!=fa[i][v]&&fa[i][u]){
u=fa[i][u];
v=fa[i][v];
}
return fa[][u];
}
void bfs(int u)
{
memset(que,,sizeof(que));
memset(visit,,sizeof(visit));
visit[u]=true;
que[]=u;
int h=,l=;
while(h<=l)
{
int rt=que[h];
for (int i=head[rt];i;i=g[i].next)
{
int v=g[i].to;
if ( !visit[v] )
{
visit[v]=true;
depth[v]=depth[rt]+;
que[++l]=v;
}
}
h++;
}
}
int main(){
scanf("%d%d",&n,&q);
memset(g,,sizeof(g));
memset(fa,,sizeof(fa));
memset(depth,,sizeof(depth));
for(int i=;i<n;i++)
{
int f,t;
scanf("%d%d",&f,&t);
g[++num].next=head[f];
head[f]=num;
g[num].to=t;
fa[][t]=f;
if(fa[][f]==)root=f;
}
for(int j=;j<=;j++)
for(int i=;i<=n;i++)
{ fa[j][i]=fa[j-][fa[j-][i]];
}
depth[root]=;
bfs(root);
for(int i=;i<=q;i++){
{
int num1,num2;
scanf("%d%d",&num1,&num2);
printf("%d\n",lca(num1,num2));
}
}
}

二、RMQ+时间戳

这个算法理解了很久才懂

首先我们都知道如果把树看成一个无向图,那么LCA一定在u->v的最短路上。而且,LCA的点就是最短路上depth最小的点。

换句话说,就是LCA是2个点到根节点的路径的第一个交汇处。

接下来用dfs为点标号,用id[i]表示这个点第一次出现在顶点序列的标号。

接下来就是求id[u]<i<id[v]中depth的最小值啦!

这个过程可以用RMQ高速解决。

所以LCA=RMQ(depth)(u,v)

注意!这个算法比较难懂,可以先看看RMQ,理解之后再画画图,恩,就差不多了。

下面贴代码

#include <cstdio>

#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 1010
#define MAXM 100000
using namespace std;
struct Edge
{
int from, to, next;
};
Edge edge[MAXM];
int head[MAXN], edgenum;
int vs[MAXN<<];//第i次DFS访问节点的编号
int depth[MAXN<<];//第i次DFS访问节点的深度
int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标
int dfs_clock;//时间戳
int N, M, Q;//点数 边数 查询数
int dp[MAXN<<][];//dp[i][j]存储depth数组 以下标i开始的,长度为2^j的区间里 最小值所对应的下标
void init()
{
edgenum = ;
memset(head, -, sizeof(head));
}
void addEdge(int u, int v)
{
Edge E = {u, v, head[u]};
edge[edgenum] = E;
head[u] = edgenum++;
}
void getMap()
{
int a, b;
while(M--)
scanf("%d%d", &a, &b),
addEdge(a, b), addEdge(b, a);
}
void DFS(int u, int fa, int d)//当前遍历点以及它的父节点 遍历点深度
{
id[u] = dfs_clock;
vs[dfs_clock] = u;
depth[dfs_clock++] = d;
for(int i = head[u]; i != -; i = edge[i].next)
{
int v = edge[i].to;
if(v == fa) continue;
DFS(v, u, d+);
vs[dfs_clock] = u;//类似 回溯
depth[dfs_clock++] = d;
}
}
void find_depth()
{
dfs_clock = ;
memset(vs, , sizeof(vs));
memset(id, , sizeof(id));
memset(depth, , sizeof(depth));
DFS(, -, );//遍历
}
void RMQ_init(int NN)//预处理 区间最小值
{
for(int i = ; i <= NN; i++)
dp[i][] = i;
for(int j = ; (<<j) <= NN; j++)
{
for(int i = ; i + (<<j) - <= NN; i++)
{
int a = dp[i][j-];
int b = dp[i + (<<(j-))][j-];
if(depth[a] <= depth[b])
dp[i][j] = a;
else
dp[i][j] = b;
}
}
}
int query(int L, int R)
{
//查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标
int k = ;
while((<<(k+)) <= R-L+) k++;
int a = dp[L][k];
int b = dp[R - (<<k) + ][k];
if(depth[a] <= depth[b])
return a;
else
return b;
}
int LCA(int u, int v)
{
int x = id[u];//比较大小 小的当作左区间 大的当作右区间
int y = id[v];
if(x > y)
return vs[query(y, x)];
else
return vs[query(x, y)];
}
void solve()
{
int a, b;
while(Q--)
{
scanf("%d%d", &a, &b);
printf("LCA(%d %d) = %d\n", a, b, LCA(a, b));
}
}
int main()
{
while(scanf("%d%d%d", &N, &M, &Q) != EOF)
{
init();
getMap();
find_depth();//DFS遍历整个树 求出所需要的信息
RMQ_init(dfs_clock - );
solve();
}
return ;
} 重点同上哈!倍增写外面!

三、tarjan算法求LCA

这个算法和之前的tarjan求强联通分量有一些差别(只是名字一样而已)

这个算法非常妙啊,其实就是从根节点dfs标记父节点,但是厉害的地方在于,在它标记完根节点后,恰好能够更新答案。

这个算法比较绕,网上有很多的图解,我就不贴了,我们来具体算法理解。

下面是tarjan的主体,h就是链表的头。。这个不讲,这里讲一下q数组,它用于存储询问,g[i].to表示他要查询的第二个节点。

void tarjan(int x){
for(int i=h[x];i;i=g[i].next)
tarjan(g[i].to),f[g[i].to]=x;
for(int i=q[x];i;i=g[i].next)
ans[g[i].to]=ans[g[i].to]>?getfa(ans[g[i].to]):x;
}

很显然,在回溯后,我们计算答案,如果没有出现g[i].to的答案,我们就将他标记为x,这是什么意思呢?其实如果标记为X,就说明在dfs时先搜到了x,再搜g[i].to,显然x即为这2者的公共祖先。

但是如果我们已经更新过ans[g[i].to],其实这说明了两个点不在同一子树上,所以我们要找到后一个节点的父亲。(重点来了!)

我们在做getfather的操作的时候,由于是先序遍历,所以我们做到这个dfs时,寻找父亲只会找到他们的公共祖先然后停止。是不是很妙?

嗯对,所以是不是很简单?

#include<iostream>
#include<cstdio>
using namespace std;
int h[],q[];
bool fa[];
int f[];
int ans[];
int n,m,num=;
struct edge{
int to,next;
}g[];
int getfa(int x){return f[x]?f[x]=getfa(f[x]):x;}
void ins(int *h,int u,int v){g[++num].next=h[u];h[u]=num;g[num].to=v;}
void tarjan(int x){
for(int i=h[x];i;i=g[i].next)
tarjan(g[i].to),f[g[i].to]=x;
for(int i=q[x];i;i=g[i].next)
ans[g[i].to]=ans[g[i].to]>?getfa(ans[g[i].to]):x;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
ins(h,x,y);
fa[y]=;
}
for(int i=;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
ins(q,x,i);ins(q,y,i);
}
for(int i=;i<=n;i++)if(!fa[i]){tarjan(i);break;}
for(int i=;i<m;i++)printf("%d\n",ans[i]);
return ;
}

算法详解(LCA&RMQ&tarjan)补坑啦!完结撒花(。◕ˇ∀ˇ◕)的更多相关文章

  1. Tarjan算法详解

    Tarjan算法详解 今天偶然发现了这个算法,看了好久,终于明白了一些表层的知识....在这里和大家分享一下... Tarjan算法是一个求解极大强联通子图的算法,相信这些东西大家都在网络上百度过了, ...

  2. 安全体系(三)——SHA1算法详解

    本文主要讲述使用SHA1算法计算信息摘要的过程. 安全体系(零)—— 加解密算法.消息摘要.消息认证技术.数字签名与公钥证书 安全体系(一)—— DES算法详解 安全体系(二)——RSA算法详解 为保 ...

  3. 算法进阶面试题01——KMP算法详解、输出含两次原子串的最短串、判断T1是否包含T2子树、Manacher算法详解、使字符串成为最短回文串

    1.KMP算法详解与应用 子序列:可以连续可以不连续. 子数组/串:要连续 暴力方法:逐个位置比对. KMP:让前面的,指导后面. 概念建设: d的最长前缀与最长后缀的匹配长度为3.(前缀不能到最后一 ...

  4. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  5. kmp算法详解

    转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...

  6. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

  7. [转] KMP算法详解

    转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段.    我们这里说的K ...

  8. 【转】AC算法详解

    原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...

  9. KMP算法详解(转自中学生OI写的。。ORZ!)

    KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句 ...

随机推荐

  1. strak组件(10):批量操作

    效果图: 批量删除只是一个例子,可以根据需求定制自己想要的批量操作. 新增函数 def get_action_list(self) 钩子方法,获取要处理的批量操作的函数 def action_mult ...

  2. SPOJ1026 概率DP

    Favorite Dice BuggyD loves to carry his favorite die around. Perhaps you wonder why it's his favorit ...

  3. poj1182食物链

    Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到 ...

  4. 笔记-爬虫-selenium常用方法

    笔记-爬虫-selenium常用方法 1.      查找元素 常用的查找方法 find_element_by_name find_element_by_xpath find_element_by_l ...

  5. Too many parameters: expected 1, was given 2 Query: SELECT count(id) FROM `user` WHERE username = ?; Parameters: [org.apache.commons.dbutils.handlers.ScalarHandler@453da22c, [李明]]

    public Object getValue(String sql,Object... args) { Connection conn = null; Object obj= null; try { ...

  6. python基础之流程控制、数字和字符串处理

    流程控制 条件判断 if单分支:当一个“条件”成立时执行相应的操作. 语法结构: if 条件: command 流程图: 示例:如果3大于2,那么输出字符串"very good" ...

  7. zeppelin之连接mysql

    上面的一篇文章,对于zeppelin的使用,只是我们对于数据存储在文件中,每一次对于当我们连接数据库的时候都会有问题,今天刚好 把这个问题解决今天我们刚好来介绍如何使用zeppelin来与数据进行连接 ...

  8. Python 文本挖掘:使用情感词典进行情感分析(算法及程序设计)

    出处:http://www.ithao123.cn/content-242299.html 情感分析就是分析一句话说得是很主观还是客观描述,分析这句话表达的是积极的情绪还是消极的情绪.   原理 比如 ...

  9. com.squareup.okhttp.Interceptor

    retrift 集成了okhttp,所以,我们以后就不用再单独的引用http的jar 了. 但是,今天遇到一个问题,就是okhttp是这样设置一些intercept的: private static ...

  10. PowerShell技巧:使用XPath语法查询XML文件

    [TechTarget中国原创] XML是存储结构化数据的一个很好的途径,但是想要让数据在其中发挥作用又会有些困难.每一种语言都有其特定方式来查询XML文件中的命名空间.元素及属性.PowerShel ...