树上倍增

倍增的思是二进制,二进制需要进行位运算
        首先开一个n×logn的数组,比如fa[n][logn],其中fa[i][j]表示i节点的第2^j个父亲是谁。

然后,我们会发现有这么一个性质:   fa[i][j]=fa [fa[i][j-1]] [j-1]

用文字叙述为:i的第2^j个父亲 是 i的第2^(j-1)个父亲的第2^(j-1)个父亲。

个人理解这个性质 认为 : 2^j = 2^(j-1) + 2^(j-1)   

求 i 的第K个父亲  需要将K拆分分成多个满足 2^n 的数相加

int father(int i,int k)
{
    for(int x=0;x<=int(log2(k));x++)
        if((1<<x)&k)    //(1<<x)&k可以判断k的二进制表示中,第x位上是否为1  ,1左移x位与K的x位 进行与(&)计算。
i=fa[i][x]; //把i往上 加一个k满足的2^n, 当循环结束,所有的2^n都加上,即找到了第K个父亲。 return i; }

用树上倍增的时候,用dfs预处理出 每一个数的所有的父节点,即fa[n][logn] 数组,便于后面查询父节点。

如果待处理的树有n个节点,那么最多有一个节点会有2^(logn)个父亲,所以我们的fa数组第二维开logn就够了。

(depth[i]表示i的深度,这个可以一起处理出来,以后要用)

//nowp=nowposition  fa=father 
void dfs(int nowp,int fa){
    depth[nowp] =depth[fa]+1; 
    father[nowp][0]=fa;
    for(int j=1;j <=lg[depth[nowp]]+1;++j)
        father[nowp][j]=father [father[nowp][j-1]][j-1];
    for(int i=0;i<G[nowp].size();++i){
        if(G[nowp][i]!=fa)
            dfs(G[nowp][i],nowp); 
    } 
}

 

这样,我们在nlogn的时间内可以通过一遍dfs处理出这棵树的相关信息。 倍增的应用中,最基础的应该就是求LCA(最近公共祖先),时间复杂度是logn。

对于求u、v的LCA,我们可以先把u、v用倍增法把深度大的提到和另一个深度相同。如果此时u、v已经相等了,表示原来u、v就在一条树链上,直接返回此时的结果。

        如果此时u、v深度相同但不等,则证明他们的lca在更“浅”的地方,此时需要把u、v一起用倍增法上提到他们的父亲相等。为啥是提到父亲相等呢?因为倍增法是一次上提很多,所以有可能提“过”了,如果是判断他们本身作为循环终止条件,就无法判断是否提得过多了,所以要判断他们父亲是否相等。

int LCA(int u,int v){
    if(depth[u]<depth [v]) swap(u,v);
//int dc = depth[u]-depth[v];
//    for(int j=0;j<maxbit;++j)
//    {
//        if((dc>>j)&1) u=father[u][j];
//    }
    while(depth[u]!=depth[v]){
        u=father[u][lg[depth[u]-depth[v]]];
    }
    if(u ==v) return u;
    for(int j= lg[depth[u]];j>=0;--j){
        if(father[u][j]!=father[v][j]){
            u=father[u][j];
            v=father[v][j];
        }
        
    }
    return father[u][0]; 
} 

 

树上倍增的真正意义

例题: 给你一棵树和两个点x和y,求这两点间路径的路径最大/小的点权/边权

我们可以设dis[i][j] 表示i到他的第2^j 个祖先的路径最大值,就可以边求LCA 边顺便求出两点距离

还有求两点的路径上路径权值和呢?

设sum[i][j] 表示i到他的第2^j个祖先的路径权值和,同时也可边求LCA边求和,

因为 sum[i][j]=sum[i][j−1]+sum[anc[i][j−1]][j−1]) 

像RMQ求LCA ,是无法解决这类问题的

参考链接:

https://blog.csdn.net/saramanda/article/details/54963914

https://blog.csdn.net/enjoy_pascal/article/details/78277008

 

 求LCA的纯模板 洛谷3379

#include <iostream> 
#include <vector>
using namespace std;
const int maxn =5e5+5;
const int maxbit =32;
vector<int> G[maxn];
int depth[maxn],lg[maxn],father[maxn][maxbit];

//nowp=nowposition  fa=father 
void dfs(int nowp,int fa){
    depth[nowp] =depth[fa]+1; 
    father[nowp][0]=fa;
    for(int j=1;j <=lg[depth[nowp]]+1;++j)
        father[nowp][j]=father [father[nowp][j-1]][j-1];
    for(int i=0;i<G[nowp].size();++i){
        if(G[nowp][i]!=fa)
            dfs(G[nowp][i],nowp); 
    } 
}

int LCA(int u,int v){
    if(depth[u]<depth [v]) swap(u,v);
//int dc = depth[u]-depth[v];
//    for(int j=0;j<maxbit;++j)
//    {
//        if((dc>>j)&1) u=father[u][j];
//    }
    while(depth[u]!=depth[v]){
        u=father[u][lg[depth[u]-depth[v]]];
    }
    if(u ==v) return u;
    for(int j= lg[depth[u]];j>=0;--j){
        if(father[u][j]!=father[v][j]){
            u=father[u][j];
            v=father[v][j];
        }
        
    }
    return father[u][0]; 
} 
 
int main ()
{
//    std::ios::sync_with_stdio(flase);
    lg[0]=-1; 
    for(int i=1; i<maxn;++i)
    { 
        lg[i]=lg[i>>1]+1;  //lg2n 向下取整。 
    }
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    int x,y;
    for(int i=1; i<= n-1;i++)
    {
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    } 
    dfs(s,0);
    while(m--)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
} 
View Code

 参考 视频链接:https://www.bilibili.com/video/av41067872?p=2

posted @ 2019-12-02 21:41  Young-children  阅读(447)  评论(0编辑  收藏  举报