zoukankan      html  css  js  c++  java
  • 与图论的邂逅08:树上倍增

    什么是树上倍增?

    顾名思义,就是在树上成倍地增长,可以用于解决一些静态树的查询问题。


    放出例题:给定一棵根节点为1的n个节点的树,并给出树上两个点u,v,求它们的最近公共祖先。

    我们可以预处理出每个点的父亲节点fa和深度dep,然后选择u,v中深度较大者不断地往父亲节点走,当u,v深度相同时判断:u,v是否为同一个点。如果是,那么答案就是u或者v;否则,u,v同时向着各自的父亲节点走,当两个点的父亲节点相同时,fa[u]或者fa[v]就是答案。时间复杂度为O(N)。

    //口胡一段代码(未检查)
    int fa[maxn],dep[maxn];
    //预处理部分,时间复杂度为O(N)
    void dfs_getfa(int u,int pre){
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==fa[u]) continue;
            dep[v]=dep[u]+1,fa[v]=u;
            dfs_getfa(v,u);
        }
    }
    //求lca部分,时间复杂度为O(N)
    inline int lca(int u,int v){
        if(dep[u]>dep[v]) swap(u,v);
        while(dep[fa[v]]>=dep[u]) v=fa[v];
        if(u==v) return u;
        while(fa[u]!=fa[v]) u=fa[u],v=fa[v];
        return fa[u];
    }
    

    现在问题改一下,仍然给你一棵根节点为1的n个点的树,下面有m个询问,每个询问都给出两个点u,v,求每个询问的lca(u,v)。1≤n,m≤500000。

    如果还是之前的做法,那么时间复杂度就是O(NM),肯定会爆掉对吧,,,,,,所以这里我们将会用到一种算法——树上倍增。之前只是一步一步地跳实在太慢,我们一次跳个2的k次方步如何?根据二进制转化的思想,一个数x总能背拆成如下的样子:

    [x=2^{k_1}+2^{k_2}+2^{k_3}+...... ]

    所以每次跳2的若干次方,我们总能跳到想要的位置。还是用上面的思路,先预处理出深度dep和数组fa(i,j),表示节点i往上跳2的j次方步到达的节点,那么f(i,j)=f(f(i,j-1),j-1),初始化f(i,j)为i的父亲节点。然后选择深度大的节点先跳,再两个点一起跳。时间复杂度为O((N+M)logN)。

    int fa[maxn][20],dep[maxn],maxdep;
    //预处理部分,时间复杂度为O(NlogN)
    void dfs_getfa(int u,int pre){
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==pre) continue;
            dep[v]=dep[u]+1,fa[v][0]=u;
            for(register int j=1;j<=maxdep;j++) fa[v][j]=fa[fa[v][j-1]][j-1];
            dfs_getfa(v,u);
        }
    }
    //求lca部分,单次时间复杂度为O(logN)
    inline int lca(int u,int v){
        if(dep[u]>dep[v]) swap(u,v);
        for(register int i=maxdep;i>=0;i--) if(dep[fa[v][i]]>=dep[u]) v=fa[v][i];
        if(u==v) return u;
        for(register int i=maxdep;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
        return fa[u][0];
    }
    //main函数中
    	maxdep=(int)log(n)/log(2)+1;
    

    上面是树上倍增最简单的实例。下面再给出一道例题:

    给出一棵n个节点的树,下面有m次询问,每次询问给出树上两个点u,v,求u到v的路径上边权的最大值。

    明确思路。虽然路径上有很多个点,但我们可以用唯一的三个点来确定这条路径——u,v和lca(u,v)。所以问题可以转化为:求出u到lca(u,v)的路径上边权的最大值为max1,并求出v到lca(u,v)的路径上边权的最大值max2,答案为max(max1,max2)。而我们上面单可以跳到lca罢了。可以类比着求fa(i,j)的做法,设max_edge(i,j)表示节点i到f(i,j)的路径上边权最大值,那么显然:max_edge(i,j)=max(max_edge(i,j-1),max_edge(fa(i,j-1),j-1)),初始化max_edge(i,0)为i和i的父亲节点之间的边的边权。然后我们倍增地往lca跳,统计跳过的地方的边权最大值即可,时间复杂度也是O((N+M)logN)。

    int fa[maxn][20],max_edge[maxn][20],dep[maxn],maxdep;
    void dfs_getfa(int u,int pre){
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==pre) continue;
            dep[v]=dep[u]+1,fa[v][0]=u,max_edge[v][0]=e[i].dis;
            for(register int j=1;j<=maxdep;j++) fa[v][j]=fa[fa[v][j-1]][j-1],max_edge[v][j]=max(max_edge[v][j-1],max_edge[fa[v][j-1]][j-1]);
            dfs_getfa(v,u);
        }
    }
    inline int lca(int u,int v){
        if(dep[u]>dep[v]) swap(u,v);
        int ans=-INF;
        for(register int i=maxdep;i>=0;i--) if(dep[fa[v][i]]>=dep[u]) ans=max(ans,max_edge[v][i]),v=fa[v][i];
        if(u==v) return ans;
        for(register int i=maxdep;i>=0;i--) if(fa[u][i]!=fa[v][i]) ans=max(ans,max(max_edge[u][i],max_edge[v][i])),u=fa[u][i],v=fa[v][i];
        return max(ans,max(max_edge[u][0],max_edge[v][0]));
    }
    //main函数中
    	maxdep=(int)log(n)/log(2)+1;
    

    求最小值或者边权和也是类似的做法。



    再来一道例题:给出一棵n个节点的树,下面有m次操作,"1,i,w"表示修改编号为i的边的边权为w,"2,u,v"表示求节点u,v之间的路径上边权最大值。

    这道题倍增就做不了了,因为无法快速修改max_edge数组。这得让树剖来做,时间复杂度可以做到O(MlogNlogN)。所以,树上倍增只适合做静态树(不带修改的)上的问题。当然树剖也可以做静态树的问题,但树上倍增表现更好(除了求lca).

  • 相关阅读:
    在DevExpress程序中使用SplashScreenManager控件实现启动闪屏和等待信息窗口
    使用Setup Factory安装包制作工具制作安装包
    PostgreSQL介绍以及如何开发框架中使用PostgreSQL数据库
    在DevExpress中使用CameraControl控件进行摄像头图像采集
    读取数据库信息构建视图字段的备注信息,方便程序代码生成
    混合框架中Oracle数据库的还原处理操作
    使用图片视频展示插件blueimp Gallery改造网站的视频图片展示
    .NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用
    在Winform界面菜单中实现动态增加【最近使用的文件】菜单项
    文字处理控件TX Text Control的使用
  • 原文地址:https://www.cnblogs.com/akura/p/10890805.html
Copyright © 2011-2022 走看看