初级倍增算法(模拟+讲解)

倍增,顾名思义就是一倍一倍的增加。举个例子你每次可以跳2的k次方步。现在你要跳到距离7步的地方。

跳8步 跳过了(不跳)

跳4步 没跳到(跳)

跳2步 没跳到 (跳)

跳1步 跳到 (停)

这里就只跳了3次 比普通的7次跳发优化了4次;

如果数据很大就是O(n) 和 O(logn)的区别了;

这里主要讲RMQ和LCA的2种简单算法

RMQ(区间最值查询)是一个预处理O(n*logn)区间极值O(1)查询任意区间极值的工具

主要思想就是区间dp出每个点起2的k次方的长度内的极值。运用大区间的极值由小区间得到,同时大区间的答案可以由小区间随意组合得到。比如我们已经预处理1为起点长度为4的答案,和2为起点向后4的答案,我们查询区间1到5的极值就可以比较1-4区间和2-5区间的答案来得到1-5的答案;

预处理的代码如下

f数组的意思是以i为起点2的j次方长度的区间的极值是多少

void ST(int n)///预处理
{
    for(int i=1;i<=n;i++)
        f[i][0]=a[i];///初始化以自己为起点2的0次方长的区间就是自己
    for(int j=1;(1<<j)<=n;j++)///枚举区间长度
    {
        for(int i=1;i+(1<<j)-1<=n;i++)///枚举起点
        f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
}

模拟一下:我们要得到f[1][1]就可以通过f[1][0]和f[2][0]得到 f[1][2]可以由f[1][1]和f[3][1]得到。由于我们是小区间得到大区间,所以比如求区间大小为8的时候,我们已经处理了所以4大小的区间了

查询极值如下

int RMQ(int l,int r)
{
       int k=trunc(log2( r-l+1 ));///向下取整
       printf("k=%d\n",k);

       return max(  f[l][k],  f[r-(1<<k)+1][k]   );
}

代码处的k是找2个小一点的区间可以覆盖玩需要查询的区间,比如查找5长度的区间,我们就找2个长度为4的区间完全覆盖查询的区间。为什么能这样,举个例子1 2 4可以任意组合出1-7之内的所有数,正是这个性质才使得这个算法可以存在。

LCA(最近公共祖先)也可以认为在树上找最短路,因为找到了最近公共祖先也就等于找到了最短路,维护一个dis[]数组表示所有节点到根节点的距离,u到v的最短距离就是dis[u]+dis[v]-2dis[LCA(u,v)];

这个算法利用的是思想是找到u和v第一个不同祖先不同的位置,然后这个位置向上走一步就是最近公共的祖先。

但是u和v不在同一个深度肯定不行,所有需要dfs求去所有节点的深度

需要注意的是根据不同情况DFS是必须找到这个树的根节点,从根节点开始搜索

怎么找到根节点就开自己开标记数组处理啦!

void DFS(int u)
{
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(vis[v]==0)
        {
            vis[v]=1;///标记
            deep[v]=deep[u]+1;
            DFS(v);
          
        }
    }
}

然后要做的就是倍增预处理每个节点上面2的k次方个祖先节点是什么

void ST(int n)///倍增预处理i 节点上面1<<j步的节点
{
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i<=n;i++)
          f[ i ] [ j ]  =  f[  f[i][j-1]   ]  [ j-1 ];
}

需要注意的是输入边的时候就把f[i][0]初始化好,f[i][0]表示i节点上面2的0次方的祖先是什么,输入的时候很容易就初始化好了。

举个例子

NULL


f[1][0]=0表示没有祖先,这个1节点就是根
f[2][0]=1表示2号节点祖先是1
以此内推f[4][0]=2  f[5][0]=2  f[3][0]=1
处理f[4][1]的时候就可以通过  f[ f[4][0]] [ 0 ]得到
f[4][0]=2 所以 f[4][1]=f[2][0]就意味着  i的上面2个节点可以通过i的上面1个节点的再跳1个节点得到 同理4个节点可以跳2次2个节点得到.

做完准备工作后就可以很高效的查询任意2个点的公共祖先了

int LCA(int u,int v)
{
    if(deep[u]<deep[v])///默认u的深度大一点简化后面的问题
        swap(u,v);
    int h=deep[u]-deep[v];///求出高度差
    for(int i=0;i<20;i++)///这个操作可以将u节点向上提升任意高度,觉得难懂就需要自己模拟一下过程
    {
        if(  h&(1<<i) )///二级制位上存在1就提升 1<<i步
          {
               u=f[u][i];
          }
    }
    if(u==v)///如果u==v表示 u就是v下面的分支节点
        return u;
    for(int i=19;i>=0;i--)///找到第一个不相同的节点
    {
        if( f[u][i]!=f[v][i] )///还是利用了 1 2 4 可以任意组合出1-7所有的步数
        {
            u=f[u][i];
            v=f[v][i];
        }
    }
    return f[u][0];///第一个不相同的节点的上一个就是最近公共祖先
}

 

  • 26
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值