LCA --- 常规的三种算法

模板题,后面的三种方法都可以做,模板也是基于这道题的

最常用的就是倍增:
1:LCA – 倍增(在线算法)比RMQ和tarjan算法都好写, 并且复杂度不高, 预处理nlogn, 询问logn.

up[i][j] 代表i的第2^j个祖先是谁. maxx[i][j] 表示i到其第2^j个祖先的路径上的最大值.
那么询问树上任意两点之间路径的最大值就可以在logn的时间内求出来.
模板:

const int maxn = 1e5 + 5;
int up[maxn][23], maxx[maxn][23];
int dep[maxn], dis[maxn];
int cnt, head[maxn];
int n, m, q;
struct node {
    int to, next, w;
}e[maxn<<1];
void init() {
    Fill(head,-1); Fill(dis,0);
    Fill(up,0); Fill(dep,0);
    cnt = 0; Fill(maxx, 0);
}
void add(int u, int v, int w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt++;
}
void dfs(int u,int fa,int d) {
    dep[u] = d + 1;
    for(int i = 1 ; i < 20 ; i ++) {
        up[u][i] = up[up[u][i-1]][i-1];
       // maxx[u][i] = max(maxx[up[u][i-1]][i-1], maxx[u][i-1]);
    }
    for(int i = head[u] ; ~i ; i = e[i].next) {
        int to = e[i].to;
        if(to == fa) continue;
        dis[to] = dis[u] + e[i].w;
        up[to][0] = u;
       // maxx[to][0] = e[i].w;
        dfs(to, u, d+1);
    }
}
int LCA_BZ(int u,int v) {
    int mx = 0;
    if(dep[u] < dep[v]) swap(u,v);
    int k = dep[u] - dep[v];
    for(int i = 19 ; i >= 0 ; i --) {
        if((1<//  mx = max(mx, maxx[u][i]);
            u = up[u][i];
        }
    }
    if(u == v) return u;
    for(int i = 19 ; i >= 0 ; i --) {
        if(up[u][i] != up[v][i]){
        //  mx = max(mx, maxx[u][i]);
        //  mx = max(mx, maxx[v][i]);
            u = up[u][i];
            v = up[v][i];
        }
    }
//     return max(mx, max(maxx[u][0], maxx[v][0]));
    return up[u][0];
}
int kth(int u, int v, int k) {  // 算u到v的路径的第k个结点.
    --k; int f = LCA_BZ(u, v);
    if (dep[u] - dep[f] < k) {
        k = dep[v] - dep[f] - k + dep[u] - dep[f];
        swap(u, v);
    }
    for (int i = 19 ; i >= 0 ; i --) {
        if (k & (1 << i)) u = up[u][i];
    }
    return u;
}
void solve() {
    scanf("%d%d",&n,&m);
    init();
    for(int i = 1 ; i < n ; i ++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w); add(v, u, w);
    }
    //maxx[1][0] = 0;
    dfs(1, -1, 0);
    while(m--) {
        int u, v;
        scanf("%d%d",&u, &v);
        int res = dis[u] + dis[v] - 2 * dis[LCA_BZ(u,v)];
        printf("%d\n", res);
    }
}

//理解离线算法的核心: 在dfs过程中, 一定会遍历到每两个点的的LCA, 并且由于顺序问题, 一定不会遍历到他们的LCA的祖先, 所以就可以当遍历到第二个相关的点时就可以直接求出了他们LCA了.

tarjan离线这篇博客讲的很好
2: LCA – tarjan 算法 (离线算法), 是把所有询问都存下来, 一次性回答完, 比较好懂, 复杂度O(n+q),就是麻烦再要把答案排好序输出. 其他的还好.
有什么不懂的地方看看那篇博客.

@@@模板

const int maxn=1e5+5;
int head_node[maxn],head_query[maxn];
bool vis[maxn];
int dis[maxn],pre[maxn];
int cnt_node,cnt_query;
int n,m;
struct node
{
    int to,next,w;
}s[maxn*2];

struct query
{
    int u,to,next,lca;
}ans[maxn*10];

int Find(int x)
{
    return pre[x] == x ? x : pre[x] = Find(pre[x]);
}

void Un(int u,int v)
{
    u = Find(u);
    v = Find(v);
    if(u != v)
        pre[v] = u;
}

void init()
{
    Fill(head_node,-1);
    Fill(head_query,-1);
    Fill(vis,false);
    Fill(dis,0);Fill(s,0); Fill(ans,0);
    for(int i=1;i<=n;i++)
        pre[i] = i;
    cnt_node = cnt_query = 0;
}

void add_node(int u,int v,int w)
{
    s[cnt_node].to = v;
    s[cnt_node].w = w;
    s[cnt_node].next = head_node[u];
    head_node[u] = cnt_node++;
}

void add_query(int u,int v)
{
    ans[cnt_query].u = u;
    ans[cnt_query].to = v;
    ans[cnt_query].next = head_query[u];
    head_query[u] = cnt_query++;
}

void tarjan(int u)
{
    vis[u] = true;
    for(int i=head_node[u]; ~i; i = s[i].next){
        int v = s[i].to;
        int w = s[i].w;
        if(!vis[v]){
            dis[v] = dis[u] + w;
            tarjan(v);
            Un(u,v);
        }
    }
    for(int i=head_query[u]; ~i ; i = ans[i].next){
        int v = ans[i].to;
        if(vis[v]){
            ans[i].lca = ans[i^1].lca = Find(v);
        }
    }
}

void solve()
{
    scanf("%d%d",&n,&m);
    init();
    for(int i=0;i1;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_node(u,v,w);
        add_node(v,u,w);
    }
    for(int i=0;i<m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add_query(u,v);
        add_query(v,u);
    }
    tarjan(1);
    for(int i=0;i2){
        int res = dis[ans[i].u] + dis[ans[i].to] - 2 * dis[ans[i].lca];
        printf("%d\n",res);
    }
}

!!!解释版

const int maxn=1e5+5;
int head_node[maxn],head_query[maxn];
bool vis[maxn];
int dis[maxn],pre[maxn];
int cnt_node,cnt_query;
int n,m;
struct node
{
    int to,next,w;
}s[maxn*2];

struct query
{
    int u,to,next,lca;
}ans[maxn*10];

int Find(int x)    //并查集.
{
    return pre[x] == x ? x : pre[x] = Find(pre[x]);
}

void Un(int u,int v)
{
    u = Find(u);
    v = Find(v);
    if(u != v)
        pre[v] = u;
}

void init()
{
    Fill(head_node,-1);
    Fill(head_query,-1);
    Fill(vis,false);
    Fill(dis,0);Fill(s,0); Fill(ans,0);
    for(int i=1;i<=n;i++)
        pre[i] = i;
    cnt_node = cnt_query = 0;
}

void add_node(int u,int v,int w)    //建立搜索边.
{
    s[cnt_node].to = v;
    s[cnt_node].w = w;
    s[cnt_node].next = head_node[u];
    head_node[u] = cnt_node++;
}

void add_query(int u,int v)   //建立答案边.
{
    ans[cnt_query].u = u;
    ans[cnt_query].to = v;
    ans[cnt_query].next = head_query[u];
    head_query[u] = cnt_query++;
}

void tarjan(int u)
{
    vis[u] = true;
    for(int i=head_node[u]; ~i; i = s[i].next){
        int v = s[i].to;
        int w = s[i].w;
        if(!vis[v]){
            dis[v] = dis[u] + w;
            tarjan(v);
            Un(u,v);    //表示v的爸爸的是u.
        }
    }
    for(int i=head_query[u]; ~i ; i = ans[i].next){    //check每一个询问,看是否可以回答.
        int v = ans[i].to;
        if(vis[v]){
            ans[i].lca = ans[i^1].lca = Find(v);
            //因为存的时候相关的边时挨着存的,也就是^1的关系.
        }
    }
}

void solve()
{
    scanf("%d%d",&n,&m);
    init();
    for(int i=0;i1;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_node(u,v,w);    //双向边
        add_node(v,u,w);
    }
    for(int i=0;iint u,v;
        scanf("%d%d",&u,&v);
        add_query(u,v);     //存询问.方便一次性全部输出答案.
        add_query(v,u);
    }
    tarjan(1);
    for(int i=0;i2){
        int res = dis[ans[i].u] + dis[ans[i].to] - 2 * dis[ans[i].lca];
        printf("%d\n",res);
    }
}

ST在线这篇博客讲的很好
3: LCA-RMQ 在线算法(即问一个答一个)借用上面那张图(一般不用)
LCA --- 常规的三种算法_第1张图片
LCA --- 常规的三种算法_第2张图片

看完这幅图, 我觉得你就应该懂了. 给出代码实现: (注意不是很懂某些地方为什么处理时, 请看看那篇我推荐的博客和解释版) 关键在于如何充分利用深度与遍历序列下标的关系!!! 因为深度所对应的位置的pre就是我们要求的那个点.

@@@模板

const int maxn=4e4+5;
int head[maxn], first[maxn] ;
int pre[2*maxn], deep[2*maxn];
int dis[maxn];
int dp[maxn*2][25];
bool vis[maxn];
int n,m;
int cnt,tot;
struct node
{
    int to,next,w;
}s[maxn*2];

void init()
{
    Fill(vis,false); Fill(head,-1);
    Fill(first,0); Fill(dp,0);
    Fill(deep,0); Fill(pre,0);
    cnt = tot = 0;
}

void add(int u,int v,int w)
{
    s[cnt].to = v;
    s[cnt].w = w;
    s[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u,int dep)
{
    vis[u] = true; pre[++tot] = u;
    first[u] = tot; deep[tot] = dep;
    for(int i=head[u] ; ~i ; i=s[i].next){
        int v = s[i].to; int w = s[i].w;
        if(!vis[v]){
            dis[v] = dis[u] + w;
            dfs(v,dep+1);
            pre[++tot] = u;
            deep[tot] = dep;
        }
    }
}

void RMQ_ST(int len)
{
    for(int i=1;i<=len;i++)
        dp[i][0] = i;
    for(int j=1;(1<for(int i=1;i+(1<1<=len;i++){
            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
            dp[i][j] = deep[a] < deep[b] ? a : b ;
        }
    }
}

int Find(int l,int r)
{
    int k = log2(r-l+1);
    int a = dp[l][k], b = dp[r-(1<1][k];
    return deep[a] < deep[b] ? a : b ;
}

int LCA(int u,int v)
{
    int x = first[u], y = first[v];
    if(x > y) swap(x,y);
    int res = Find(x,y);
    return pre[res];
}

void solve()
{
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;iint u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,1);
    RMQ_ST(2*n-1);
    while(m--){
        int u,v;
        scanf("%d%d",&u,&v);
        int res = dis[u] + dis[v] - 2*dis[LCA(u,v)];
        printf("%d\n",res);
    }

}

!!!解释版

const int maxn=4e4+5;
int head[maxn], first[maxn] ;
int pre[2*maxn], deep[2*maxn]; //pre保存的是遍历的节点序列.deep是对应pre的每一个节点所在的深度.
int dis[maxn];
int dp[maxn*2][25];  //保存的信息是最小深度的位置关系了,这样才方便查询.
bool vis[maxn];
int n,m;
int cnt,tot;
struct node    //存边的信息.
{
    int to,next,w;
}s[maxn*2];

void init()
{
    Fill(vis,false); Fill(head,-1);
    Fill(first,0); Fill(dp,0);
    Fill(deep,0); Fill(pre,0);
    cnt = tot = 0;
}

void add(int u,int v,int w)
{
    s[cnt].to = v;
    s[cnt].w = w;
    s[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u,int dep)   //遍历这棵树
{
    vis[u] = true; pre[++tot] = u;    //记下每一个对应的pos.
    first[u] = tot; deep[tot] = dep;
    for(int i=head[u] ; ~i ; i=s[i].next){
        int v = s[i].to; int w = s[i].w;
        if(!vis[v]){
            dis[v] = dis[u] + w;
            dfs(v,dep+1);
            pre[++tot] = u;
            deep[tot] = dep;
        }
    }
}

void RMQ_ST(int len)   //len就是最长dfs遍历的长度,就是2*n-1.
{
    for(int i=1;i<=len;i++)   //存的是遍历的序列,后面更新时是把dp更新成了deep的位置,即tot,
        dp[i][0] = i;       //所以越往后面dp就是求的位置区间的一个最小值,也就是我们所要求的.
    for(int j=1;(1<for(int i=1;i+(1<1<=len;i++){
            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
            dp[i][j] = deep[a] < deep[b] ? a : b ;
        }
    }
}

int Find(int l,int r) //这个询问仅仅是返回一个位置,即LCA所在序列数组的位置,pre[res]才是LCA的标号
{
    int k = log2(r-l+1);
    int a = dp[l][k], b = dp[r-(1<1][k];   //保存的是编号,因为deep与pre数组同时保存了深度和编号
    //所以可以通过直接返回放回对应深度的位置就行了, 不懂的话请具体看看那幅图.
   // printf("@@@@%d %d %d %d\n",deep[a],deep[b],a,b);
    return deep[a] < deep[b] ? a : b ;
}

int LCA(int u,int v)  //求u,v的LCA.
{
    int x = first[u], y = first[v];
    if(x > y) swap(x,y);
    int res = Find(x,y);
    return pre[res];
}

void solve()
{
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;iint u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,1);
    RMQ_ST(2*n-1);
    while(m--){
        int u,v;
        scanf("%d%d",&u,&v);
        int res = dis[u] + dis[v] - 2*dis[LCA(u,v)];
        printf("%d\n",res);
    }

}

你可能感兴趣的:(LCA/树上差分,板子)