首先,众所周知,求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. SQL Server 数据库内部版本号

    数据库还原或版本升级出现版本错误时可参考 可以利用SQL语句查看本机数据库版本:select @@VERSION 如果你不确定是源数据库的版本,下面的映射表的SQL Server版本的内部版本号,以便 ...

  2. Educational Codeforces Round 43 E. Well played!(贪心)

    E. Well played! time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...

  3. [Codeforces947D]Riverside Curio(思维)

    Description 题目链接 Solution 设S[i]表示到第i天总共S[i]几个标记, 那么满足S[i]=m[i]+d[i]+1 m[i]表示水位上的标记数,d[i]表示水位下的标记数 那么 ...

  4. CodeForces 547E Mike and Friends AC自动机 主席树

    题意: 给出\(n\)个字符串\(s_i\)和\(q\)个询问: \(l,r,k\):\(\sum\limits_{i=l}^{r}count(i, k)\),其中\(count(i,j)\)表示\( ...

  5. linux下创建用户 费元星站长

    linux下创建用户(一) Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统.用户的账号一方面可以帮助系 ...

  6. 当Hadoop 启动节点Datanode失败解决

    Hadoop 启动节点Datanode失败解决 [日期:2014-11-01] 来源:Linux社区  作者:shuideyidi [字体:大 中 小] 当我动态添加一个Hadoop从节点的之后,出现 ...

  7. Intellij 设置只更新静态文件(js、view、css)的方法

    1.打开 Tomcat Run/Debug configuration 2.打开Deployment标签 3.在“Deploy at Server Startup” 中,移出现有的.war 包 4.点 ...

  8. github+git提交 基础用法

    git版本管理基本用法: 安装就不用说了 随便一搜 安装完 妥妥的.下边说的是在github从新建一个项目开始: 1.首先打开自己的github地址,如下图所示 点加号 选 New repositor ...

  9. MySQL高可用之MHA安装

      Preface       MasterHA is a tool which can be used in MySQL HA architecture.I'm gonna implement it ...

  10. ffifdyop

    题目地址:http://www.shiyanbar.com/ctf/2036 后台登陆 上来看到这个界面,果断先看一波源代码. 看到是拼接字符串进行sql查询,就想到了注入了. 但是很不幸的是md5( ...