• 倍增入门


    今天看不懂倍增代码,只好学了呀

    首先

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

    跳8步 跳过了(不跳)

    跳4步 没跳到(跳)

    跳2步 没跳到 (跳)

    跳1步 跳到 (停)

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

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

    这里主要讲RMQ和LCA的2种简单算法
    RMQ:查找区间极值,而且还可以优化的

    • 预处理的代码如下

      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 ",k);

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

       1 #include<iostream>
       2 #include<cmath>
       3 #include<cstdio>
       4 using namespace std;
       5 int a[2000010];
       6 int f[2000010][21];
       7 int RMQ(int l,int r)
       8 {
       9     int k=trunc(log2(r-l+1));
      10     return min(f[l][k],f[r-(1<<k)+1][k]);
      11 }
      12 int main ()
      13 {
      14     int n,m;
      15     cin>>n>>m;
      16     for (int i=1;i<=n;i++)
      17         scanf("%d",&a[i]);
      18     for (int i=1;i<=n;i++)
      19        f[i][0]=a[i];
      20     for (int j=1;(1<<j)<=n;j++)
      21        for (int i=1;i+(1<<j)-1<=n;i++)
      22          f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
      23     cout<<"0"<<endl;
      24     for (int i=2;i<=n;i++)
      25         printf("%d
      ",RMQ(max(1,i-m),i-1));
      26  } 
    • LCA:最近公共祖先

    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次方的祖先是什么,输入的时候很容易就初始化好了。

    举个例子

    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];///第一个不相同的节点的上一个就是最近公共祖先
    }

    为何要逼自己长大,去闯不该闯的荒唐
  • 相关阅读:
    Python 绘制图表之我见 ---一个java程序员的看法
    机器学习系统设计--1.4
    XUtils3 的 环境搭建与简单使用
    关于SAX
    TensorFlow之Varibale 使用方法
    安装Nvidia k80驱动步骤
    TensorFlow 在android上的Demo(1)
    Python 去剑式
    python编程技巧2
    python语言技巧
  • 原文地址:https://www.cnblogs.com/zjzjzj/p/11129134.html
Copyright © 2020-2023  润新知