ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树链剖分与倍增求LCA

2019-11-04 22:00:12  阅读:254  来源: 互联网

标签:ch 剖分 int top 树链 dep LCA now 节点


树链剖分与倍增求\(LCA\)

首先我要吐槽机房的辣基供电情况,我之前写了一上午,马上就要完成的时候突然停电,然后\(GG\)成了送链剖分
其次,我没歧视\(tarjan LCA\)

1.倍增求\(LCA\)

理解较为简单的一种方法,但速度略慢

倍增是啥?

每个数字都可以拆成几个二的整数次的和,我们可以找出每个数字是由哪几个二的整数次的数合成的
比如说\(14 _ {10} = 1110_2 = 1000 _2 + 100 _2 + 10 _2 = 8 _ {10} + 4 _{10} + 2 _{10}\)
那么我们如果要统计一段长度为十四的区间的最小值,我们就可以先统计前八个数的最小值,再统计之后的四个,再统计之后的两个。
我们可以用\(f[i][j]\)表示从\(i\)开始往后\(2^j\)长度的最小值
给宁康康代码

for( int i = 1; i <= 23; i++ ){
        for( rint j = 1; j <= n; j++ ){
            //a[i][j]存的是i往后2的j次长度的区间的右节点是哪儿
            f[i][j] = min( f[i][j - 1], f[a[i][j - 1]][j - 1] );
        }
    }

下面这个东西是啥意思呢

f[i][j - 1], f[a[i][j - 1]][j - 1]

\(2^j = 2^{j-1} +2^{j - 1}\) 比如说 \(2^4 = 2 ^ 3 +2 ^ 3\)

树上倍增

在每个叶节点到根节点的链上做倍增
图片演示一哈(如果打开我的博客就会发现我这种蒟蒻说不清话只会画图

应该比较显然吧???
我们在求\(LCA\)前\(dfs\)一遍,统计出每个叶节点的\(f[i][1]\)(也就是父节点)和\(dep[i]\)(就是该节点所处深度,规定根节点深度为1)。然后跑一遍倍增,预处理每个叶节点的向上\(2^i\)个祖宗是谁。

然后倍增求\(LCA\),我们可以先看两个点是否在同一深度,不在的话就把比较低的那个点往上走一走,直到走到同一深度。注意在跳的时候要从大到小枚举,给宁康康代码,宁再把上面的\(14\)那个例子带进去从\(1\)到\(20\)枚举一下子就懂了

inline int lca( int x, int y ){
    if( dep[x] < dep[y] ) swap( x, y );
    for( rint i = 20; i >= 0; i-- ){
        if( dep[f[x][i]] >= dep[y] ) x = f[x][i];
    }
    if( x == y ) return x;
    for( rint i = 20; i >= 0; i-- ){
        if( f[x][i] == f[y][i] ) continue; //如果跳的一样的话就可能是LCA或者是LCA的祖先,所以先跳到最后一个不一样的,再往上跳一个
        else x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}

\(AC\)代码

#include<bits/stdc++.h>
using namespace std;
#define rint register int
int n, m, s, cnt, dep[500005], f[500005][23], head[500005];
struct edge{
    int to, nxt;
}a[500005<<1];
inline int read( void ){
    int re = 0, f = 1;
    char ch = getchar();
    while( ch > '9' || ch < '0' ){
        if( ch == '-' ) f = -1;
        ch = getchar();
    }
    while( ch >= '0' && ch <= '9' ){
        re = re * 10 + ch - '0';
        ch = getchar();
    }
    return re * f;
}
inline void addedge( int x, int y ){
    a[++cnt].to = y;
    a[cnt].nxt = head[x];
    head[x] = cnt; 
}
inline void dfs( int x, int fa ){
    dep[x] = dep[fa] + 1;
    f[x][0] = fa;   
    for( rint i = 1; ( 1 << i ) <= dep[x]; i++ ){
        f[x][i] = f[f[x][i - 1]][i - 1];
    }
    for( rint i = head[x]; i; i = a[i].nxt ){
        int v = a[i].to;
        if( v == fa ) continue;
        dfs( v, x ); 
    }
    return ;
}
inline int lca( int x, int y ){
    if( dep[x] < dep[y] ) swap( x, y );
    for( rint i = 20; i >= 0; i-- ){
        if( dep[f[x][i]] >= dep[y] ) x = f[x][i];
    }
    if( x == y ) return x;
    for( rint i = 20; i >= 0; i-- ){
        if( f[x][i] == f[y][i] ) continue;
        else x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}
int main( void ){
    n = read(); m = read(); s = read();
    for( rint i = 1; i <= n - 1; i++ ){
        int x, y; x = read(); y = read();
        addedge( x, y ); addedge( y, x );
    }   
    dfs( s, 0 );
    int u, v;
    for( rint i = 1; i <= m; i++ ){
        u = read(); v = read();
        cout << lca( u ,v ) << endl;    
    }
    return 0;
}

树链剖分

树链剖分其实有好多种剖分方法,但这里只介绍轻重边剖分

含义

一个节点只能有一个重儿子。
链 : 连续的重/轻边构成一条链。(图中从\(1\)到\(14\)即一条重链)
\(dep[i]\) : \(i\)节点的深度,规定根节点深度为\(1\)。
\(fa[i]\) : \(i\)节点的父亲。
\(son[i]\) : \(i\)节点的重儿子。
\(siz[i]\) : 以\(i\)节点为根的子树的大小。
\(top[i]\) : \(i\)所在链的根。(图中从\(1\)到\(14\)的链的根为\(1\))
重边 : 以\(i\)节点的儿子中\(siz\)最大的儿子到\(i\)的连边,即图中的粗边(该儿子也叫重儿子)。
轻边 : 处重边外的其他边。

步骤

首先\(dfs\)一遍,求出\(dep\),\(fa\),\(son\),\(siz\),容易实现,给宁康代码

inline void dfs1( int now, int father, int de ){
    siz[now] = 1; fa[now] = father; dep[now] = de;
    int maxn = -1;
    for( rint i = 0; i < vec[now].size(); i++ ){
        int v = vec[now][i];
        if( v == father ) continue ;
        dfs1( v, now, de + 1 );
        siz[now] += siz[v];
        if( siz[v] > maxn ){
            maxn = siz[v];
            son[now] = v;
        }
    }
}

然后再\(dfs\)一遍,处理出每个点的\(top\),也就是所在链的顶点,其中轻链的顶点是它自己。
给宁康康代码

inline void dfs2( int now, int topf ){
    top[now] = topf;
    if( !son[now] ) return ;
    dfs2( son[now], topf );
    for( rint i = 0; i < vec[now].size(); i++ ){
        int v = vec[now][i];
        if( v == fa[now] || v == son[now] ) continue;
        dfs2( v, v );
    }
}

如果一个点要跳到它的\(lca\),就一定会跳到它的\(lca\)所在链(废话……

那么我们要判定是否已经找到\(lca\),就只需要看当前两点\(xy\)是否在同一条链上,其实就是看两个点的\(top\)是否相等,如果不相等的话,我们就让深度大的点一次性跳完一整条重链,然后再跳一步,走上另一条重链,再重复以上比较\(top\),跳重链的过程

我们可以发现非叶节点一定在某条重链上,所以我们一次性跳完一条重链,再跳一步,就会跳上另一条重链,所以查找\(lca\)的复杂度是小于\(logn\)的

给宁康康代码

inline int lca( int x, int y ){
    while( top[x] != top[y] ){
        if( dep[top[x]] < dep[top[y]] ) swap( x, y );
        x = fa[top[x]];
    }
    if( dep[x] > dep[y] ) return y;
    return x;
}

全部代码:

#include<bits/stdc++.h>
using namespace std;
#define rint register int
int T, n, m;
int son[1000010], fa[1000010], siz[1000010], dep[1000010], top[1000010];
vector< int > vec[1000010];
inline int read( void ){
    int re = 0, f = 1; char ch = getchar();
    while( ch > '9' || ch < '0' ){
        if( ch == '-' ) f = -1;
        ch = getchar();
    }
    while( ch >= '0' && ch <= '9' ){
        re = re * 10 + ch - '0';
        ch = getchar();
    }
    return re * f;
}
inline void dfs1( int now, int father, int de ){
    siz[now] = 1; fa[now] = father; dep[now] = de;
    int maxn = -1;
    for( rint i = 0; i < vec[now].size(); i++ ){
        int v = vec[now][i];
        if( v == father ) continue ;
        dfs1( v, now, de + 1 );
        siz[now] += siz[v];
        if( siz[v] > maxn ){
            maxn = siz[v];
            son[now] = v;
        }
    }
}
inline void dfs2( int now, int topf ){
    top[now] = topf;
    if( !son[now] ) return ;
    dfs2( son[now], topf );
    for( rint i = 0; i < vec[now].size(); i++ ){
        int v = vec[now][i];
        if( v == fa[now] || v == son[now] ) continue;
        dfs2( v, v );
    }
}
inline int lca( int x, int y ){
    while( top[x] != top[y] ){
        if( dep[top[x]] < dep[top[y]] ) swap( x, y );
        x = fa[top[x]];
    }
    if( dep[x] > dep[y] ) return y;
    return x;
}
int main( void ){
    n = read(); m = read();
    for( rint i = 1; i < n; i++ ){
        int u, v; u = read(); v = read();
        vec[u].push_back( v ); 
        vec[v].push_back( u );
    }
    dfs1( 1, 1, 1 );
    dfs2( 1, 1 );
    for( rint i = 1; i <= m; i++ ){
        int x, y; x = read(); y = read(); 
        printf( "%d\n", lca( x, y ) );
    }    
    return 0;
}

如果要求树上两点最短距离,可以求\(lca\)
\(dis = dep[x] + dep[y] - 2 * lca\)

标签:ch,剖分,int,top,树链,dep,LCA,now,节点
来源: https://www.cnblogs.com/with6676/p/11795224.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有