LCA模板

LCA,也就是最近公共祖先。

一般有三种写法。

树上倍增LCA,先序DFS(欧拉序)+RMQ(ST表),DFS序+并查集。

 

一、树上倍增

首先是树上倍增LCA

注意这里N = ceil(n) 也就是n个点向下取整。

首先初始化father数组

这里father数组代表的意思是father[i][j]也就是i点到树上i+2^j上的祖先,所以当j = 0的时候,就是自己的最近祖先。

又因为树上有father[i][j]的祖先是father[i][j-1]的祖先的祖先,所以有father[i][j] = father[ father[i][j-1] ][j-1]成立。

void update(int cur, int fa)
{
    father[cur][0] = fa;
    for(int i = 1; i <= N; ++i)
        father[cur][i] = father[ father[cur][i-1] ][i-1];
}

 

有一种模板使用了dfs与求father数组相结合的比如这样

struct node
{
    int to, weight;
    node(int a, int b)
    {
        to = a; weight = b;
    }
};

void dfs(int u)
{
    for(int i = 0; i < G[u].size(); ++i)
    {
        int v = G[u][i].to;
        if(!vis[v])
        {
            depth[u] = depth[v]+1;
            vis[v] = 1;
            //dis[v] = dis[u]+G[u][i].weight; //计算两点之间最大距离时加上
            dfs(v);
        }
    }
}

void dfs2(int u)
{
    for(int j = 1; j <= N; ++j)
        for(int i = 1; i <= n; ++i)
            father[i][j] = father[ father[i][j-1] ][j-1];
    for(int i = 0; i < G[u].size(); ++i)
    {
        int v = G[u][i].to;
        if(!vis[v])
        {
            depth[u] = depth[v]+1;
            vis[v] = 1;
            //dis[v] = dis[u]+G[u][i].weight; //计算两点之间最大距离时加上
            dfs(v);
        }
    }
}

 

 其实完全没必要,直接加一个update函数就完事了

void update()
{
    for(int j = 1; j <= N; ++j)
        for(int i = 1; i <= n; ++i)
        {
            father[i][j] = father[ father[i][j-1] ][j-1];
        }
}

int dfs(int u)
{
    for(int i = 0; i < G[u].size(); ++i)
    {
        int v = G[u][i].to;
        if(!vis[v])
        {
            depth[v] = depth[u]+1;
            vis[v] = 1;
            dis[v] = dis[u]+G[u][i].weight;
            dfs(v);
        }
    }
}

 

然后就是LCA模板

int find_lca(int x, int y)
{
    if(depth[x] < depth[y]) swap(x, y); //保证x一定大于y
    for(int i = N; i >= 0; --i) if(depth[ father[x][i] ] >= depth[y]) x = father[x][i];
    if(x == y) return x;
    for(int i = N; i >= 0; --i)
        if(father[x][i] != father[y][i])
        {
            x = father[x][i]; y = father[y][i];
        }
    return father[x][0];

}

 

二、欧拉序+RMQ

首先说说欧拉序是什么

欧拉序就是从根结点出发,按dfs的顺序在绕回原点所经过所有点的顺序

2、欧拉序有怎么写?

(1)dfs到加进,dfs回加进,总共加入度遍。

 

原图来源于网络,并经过灵魂画师xhk的一发魔改。

欧拉序1为A-B-D-B-E-G-E-B-A-C-F-H-F-C-A

来源博客:https://www.cnblogs.com/stxy-ferryman/p/7741970.html


下面是欧拉序代码

先说明一下变量命名规则

#include <bits/stdc++.h>

using namespace std;
int n, m;
struct node
{
    int to, weight;
    node(int a, int b)
    {
        to = a; weight = b;
    }
};

const int MX = 40000+10;
int depth[2*MX], d[2*MX][64], f[2*MX], pos[MX], dis[MX]; //depth为深度,d(i, j)为点i到k最小的位置,
                                                         //pos为每个点第一次出现的位置,dis为每个点到根节点的位置,f为欧拉序
vector<node> G[MX];
int vis[MX];
int cnt;

 

dfs求欧拉序,深度,以及到根节点的距离(假设根为1)

int dfs(int cur, int deep) //dfs树
{
    cnt++; //从1开始
    f[cnt] = cur; pos[cur] = cnt; depth[cnt] = deep;
    for(int i = 0; i < G[cur].size(); ++i)
    {
        node v = G[cur][i];
        if(!vis[v.to])
        {
            dis[v.to] = dis[cur]+v.weight;
            vis[v.to] = 1;
            dfs(v.to, deep+1);
            cnt++;
            f[cnt] = cur; depth[cur] = deep; //欧拉序记录dfs回来后的位置
        }
    }
}

 

然后直接上LRMQ模板,找每段depth最小的点

void RMQ_init()
{
    for(int i = 1; i <= 2*n-1; ++i) d[i][0] = i; //初始化祖先为自己
    for(int j = 1; (1<<j) <= 2*n-1; ++j)
        for(int i = 1; i+(1<<j)-1 <= 2*n-1; ++i)
        {
            if(depth[ d[i][j-1] ] < depth[ d[i+(1<<(j-1))][j-1] ]) d[i][j] = d[i][j-1];
            else d[i][j] = d[i+(1<<(j-1))][j-1];
        }
}

int RMQ(int le, int ri)
{
    le = pos[le]; ri = pos[ri];
    if(le > ri) swap(le, ri); //为了求出len = ri-le+1
    int k = 0;
    while(1<<(1+k) <= ri-le+1) k++;
    if(depth[ d[le][k] ] < depth[ d[ri-(1<<k)+1][k] ]) return f[ d[le][k] ];
    else return f[ d[ri-(1<<k)+1][k] ]; //返回dp位置的点是什么
}

 

posted @ 2019-08-12 01:29  mpeter  阅读(294)  评论(0)    收藏  举报