LCA专题

详细讲解博客:http://dongxicheng.org/structure/lca-rmq/(没有代码)

求LCA(最近公共祖先)的算法有好多,按在线和离线分为在线算法和离线算法。
离线算法有基于搜索的Tarjan算法比较好,而在线算法则是基于dp的ST算法比较好。当然树链剖分也能写
先是ST算法。
这个算法是基于RMQ(区间最大最小值编号)的,而求LCA就是把树通过深搜得到一个序列,然后转化为求区间的最小编号。

参考博客:
http://blog.csdn.net/liangzhaoyang1/article/details/52549822

来两道例题:
1.hdu2586
题意:
一个村庄有 n 个房子和 n-1 条双向路,每两个房子之间都有一条简单路径。
现在有m次询问,求两房子之间的距离。

思路:
可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了。
计算方法是,记下根结点到任意一点的距离dis[i],
这样ans = dis[u] + dis[v] - 2 * dis[lca(u, v)]了

代码:

#include <bits/stdc++.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 4e4+5;
const int M = 25;

int dp[N<<1][M]; // //这个数组记得开到2*N,因为遍历后序列长度为2*n-1

struct Edge{
    int to, next, w;
}edge[N<<1];
int tol, head[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
}

void addedge(int u, int v, int w){
    edge[tol].to = v; edge[tol].w = w; edge[tol].next = head[u]; head[u] = tol++;
}

int ver[N<<1], R[N<<1], first[N], dis[N], cnt;
//ver:节点编号 R:深度 first:点编号位置 dis:距离 cnt:遍历后序列长度
void dfs(int u, int pre, int dep){
    ver[++cnt] = u; first[u] = cnt; R[cnt] = dep;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v == pre)continue;
        int w = edge[i].w;
        dis[v] = dis[u]+w;
        dfs(v, u, dep+1);
        ver[++cnt] = u;
        R[cnt] = dep;
    }
}

int rmq_init(int n){
    for(int i=1; i<=n; i++)dp[i][0] = i;
    for(int j=1; (1<<j)<=n; j++)
        for(int i=1; i+(1<<j)-1<=n; i++){
            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
            dp[i][j] = R[a]<R[b]?a:b;
        }
}

//查询最小值,中间部分是交叉的
int rmq(int l,int r){
    int k=(int)(log(r-l+1.0)/log(2.0));
    int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //返回其在ver数组中的下标
    return R[a]<R[b]?a:b;
}

int LCA(int u, int v){
    int x = first[u], y = first[v];
    if(x > y)swap(x, y);
    int ret = rmq(x, y);
    return ver[ret];
}

int in[N];
int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        mst(in, 0);
        int n, q;
        scanf("%d%d", &n, &q);
        for(int i=1; i<n; i++){
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            addedge(u, v, w);
            addedge(v, u, w);
            in[v]++;
        }
        int root;
        for(int i=1; i<=n; i++)if(!in[i])root = i;
        dis[root] = 0; cnt = 0;
        dfs(root, -1, 1);
        rmq_init(2*n-1);
        while(q--){
            int u, v;
            scanf("%d%d", &u, &v);
            int lca = LCA(u, v);
            printf("%d\n", dis[u]+dis[v]-2*dis[lca]);
        }
    }
    return 0;
}

2.POJ1330 Nearest Common Ancestors

就是直接求最近公共祖先

代码:

#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e4+5;
const int M = 25;
int dp[N<<1][M]; // //这个数组记得开到2*N,因为遍历后序列长度为2*n-1

struct Edge{
    int to, next, w;
}edge[N<<1];
int tol, head[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
}

void addedge(int u, int v, int w){
    edge[tol].to = v; edge[tol].w = w; edge[tol].next = head[u]; head[u] = tol++;
}

int ver[N<<1], R[N<<1], first[N], dis[N], cnt;
//ver:节点编号 R:深度 first:点编号位置 dis:距离 cnt:遍历后序列长度
void dfs(int u, int pre, int dep){
    ver[++cnt] = u; first[u] = cnt; R[cnt] = dep;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v == pre)continue;
        int w = edge[i].w;
        dis[v] = dis[u]+w;
        dfs(v, u, dep+1);
        ver[++cnt] = u;
        R[cnt] = dep;
    }
}

int rmq_init(int n){
    for(int i=1; i<=n; i++)dp[i][0] = i;
    for(int j=1; (1<<j)<=n; j++)
        for(int i=1; i+(1<<j)-1<=n; i++){
            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
            dp[i][j] = R[a]<R[b]?a:b;
        }
}

//查询最小值,中间部分是交叉的
int rmq(int l,int r){
    int k=(int)(log(r-l+1.0)/log(2.0));
    int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //返回其在ver数组中的下标
    return R[a]<R[b]?a:b;
}

int LCA(int u, int v){
    int x = first[u], y = first[v];
    if(x > y)swap(x, y);
    int ret = rmq(x, y);
    return ver[ret];
}

int in[N];

int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        mst(in, 0);
        int n;
        scanf("%d", &n);
        for(int i=1; i<n; i++){
            int u, v, w;
            scanf("%d%d", &u, &v);
            addedge(u, v, 1);
            addedge(v, u, 1);
            in[v]++;
        }
        int root;
        for(int i=1; i<=n; i++)if(!in[i])root = i;
        dis[root] = 0; cnt = 0;
        dfs(root, -1, 1);
        rmq_init(2*n-1);
        int u, v;
        scanf("%d%d", &u, &v);
        printf("%d\n", LCA(u, v));
    }
    return 0;
}

树链剖分的做法:
参考博客:
http://www.cnblogs.com/qilinart2/articles/5931595.html
http://www.cnblogs.com/fuyun-boy/p/6045709.html

代码:

#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e4+5;

int n, m, q;
struct Edge{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;

int a[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;  
    memset(son, -1, sizeof(son));
}

void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;  //树状数组编号从1开始
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

int LCA(int x, int y){
    //如果两个的链顶不相等,我们就把他们往一起靠
    //看那个点的链顶深度大改变那个点
    for( ; top[x]!=top[y] ; dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]);
    return dep[x]<dep[y]?x:y; //返回时要返回深度比较小的数
}

int in[N];

int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        mst(in, 0);
        int n;
        scanf("%d", &n);
        for(int i=1; i<n; i++){
            int u, v;
            scanf("%d%d", &u, &v);
            addedge(u, v);
            addedge(v, u);
            in[v]++;
        }
        int root;
        for(int i=1; i<=n; i++)if(!in[i])root = i;
        dfs1(root, -1, 0);
        getpos(root, root);
        int u, v;
        scanf("%d%d", &u, &v);
        printf("%d\n", LCA(u, v));
    }
    return 0;
}

3.洛谷 3379

树剖求LCA板子题

代码:

#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 5e5+5;

int n, m, q;
struct Edge{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;

int a[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;  
    memset(son, -1, sizeof(son));
}

void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;  //树状数组编号从1开始
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

int LCA(int x, int y){
    //如果两个的链顶不相等,我们就把他们往一起靠
    //看那个点的链顶深度大改变那个点
    for( ; top[x]!=top[y] ; dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]);
    return dep[x]<dep[y]?x:y; //返回时要返回深度比较小的数
}

int  main(){
    init();
    int n, m, s;
    scanf("%d%d%d", &n, &m, &s);
    for(int i=1; i<n; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    dfs1(s, -1, 0);
    getpos(s, s);
    while(m--){
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", LCA(x, y));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值