zoukankan      html  css  js  c++  java
  • 【倍增小总结】

    倍增小总结

      因为考试倍增题挂了一个long long,被迫写总结QAQ

      ●何谓倍增法

      简单的说就是每次步数是原来的两倍,一般用数组f[i][k]表示在i这个点走了2^k步之后的结果。

      ●倍增法的用处?

      倍增法多用于数列或者树上,进行一段较大的区间查询,把每次查询的时间由O(n)降到O(logn)。

      ●倍增法的常见应用

      一、RMQ 算法

      这个算法其实可以说是个dp了。

      1.简单介绍

      大致是在一段很长序列上进行区间查询,用st[i][k]表示从i开始后2^k个数中的最大/小值或者和。这个数组叫做st表,建立的话是O(nlogn)的时间,但查询只需要O(1)。

      2.对比数据结构

      相比于线段树来说,不仅代码更好写,并且时间更优,因为线段树常数大且每次操作都是logn。但线段树可以支持很多操作,还可以修改之类的,就很屌了。

      比上树状数组么。。

      树状数组常数也很小,并且支持修改操作,但它有一个致命的BUG,那就是它只能查询与前缀相关的东西,譬如前缀和之类的。

    如果查询区间和,直接用前缀和减去就好了,但是如果查询区间最大值,那么它就没有任何办法了。

      3.简单建立st表

      确实很简单,大致参照一个dp思路

      i~i+2^k的区间信息可由i+2^(k-1)~i+2^(k-1)+2^(k-1)和i~i+2^(k-1)合并得到。st数组定义参照介绍。

    for(int k=1;(1<<k)<=n;k++)
    
    for(int i=1;i+(1<<j)-1<=n;i++)
    
    st[i][k]=min(st[i][k-1],st[i+(1<<(k-1))][k-1]);
    

      

      值得注意的是,由于处理2^k区间长度时要用到i和i+2^(k-1)点的信息,所以先枚举k。

      4.查询没什么好提的,根据定义来就行

    int rmq(int a,int b){
    
    if(a>b)swap(a,b);
    
    int t=(int)log2(b-a+1);
    
    return min(st[a][t],st[b-(1<<t)+1][t]);
    
    }
    

      

     

      5.简单的小优化

      由于上述的2^i和log2(i)都需要计算,而log2函数的常数是比较大的,所以可以根据幂和log的性质预处理出mi[]和lg[]数组。

    此外,RMQ算法在后缀数组中用的还是比较多的。

      二、树上倍增

      1.寻找爸爸

      树上倍增这个东西呢,一般是用来求lca的,单次查询时间logn,具体的描述有点长,就不写了。

      还是大致提一下。关键是建立fa[i][k]表示i点向上数2^k个父亲是谁。查询lca(x,y)的时候得到x,y的深度,帮它们两个提到同一深度,再同时向上提找爸爸就好了。具体有些细节不再赘述。

      挂一波代码。

    void dfs(int u,int dad,){
    
        p[u][0]=dad;
        
        d[u]=d[dad]+1;
    
        for(int i=1;(1<<i)<=d[u];i++)
    
        p[u][i]=p[p[u][i-1]][i-1];
    
       for(int i=hd[u];i;i=e[i].next)
    
        if(e[i].v!=dad)dfs(e[i].v,u,e[i].w);
    
    }
    
     
    
    int lca(int x,int y){
    
        if(d[x]>d[y])swap(x,y);
    
        for(int i=20;~i;i--)
    
        if(d[x]<=d[y]-(1<<i))y=p[y][i];
    
        if(x==y)return x;
    
        for(int i=20;~i;i--){
    
        if(p[x][i]==p[y][i])continue;
    
        x=p[x][i];y=p[y][i];
    
        }
    
        return p[x][0];
    
    }
    View Code

      2.找爸爸的优化版

      预处理还是nlogn的,只不过把查询变成了RMQ O(1)算法。

      怎么做呢?

      很简单,中序遍历一遍树,记录欧拉序和每个点在欧拉序中最早出现的位置fir[i]。根据深度最小建立st表,查询lca(x,y)的时候只用查询rmq(fir[x],fir[y])就好了。证明略。

      这样的话,我们把查询时间变成了O(1),避免和数据结构一起用的时候出题人卡双log的情况。下面给出一道卡双log的例题。

      http://begin.lydsy.com/JudgeOnline/problem.php?id=2100

      题解的话看这个http://blog.csdn.net/rzo_kqp_orz/article/details/52280811

      代码给在这里

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<vector>
    #include<cstring>
    #define ll long long
    #define N 100005
    using namespace std;
    int ol[N<<2],dfn[N],dep[N],rmq[N<<2][25];
    int st[N],ed[N],fir[N],lg[N<<2];
    int n,q,ont,dnt;vector<int>s[N];
    struct node{int x,y,len;}t[N<<2];
    void dfs(int u,int pre){
        ol[++ont]=u;dfn[++dnt]=u;
        dep[u]=dep[pre]+1;st[u]=dnt;
        fir[u]=ont;
        for(int i=0;i<s[u].size();i++){
            int v=s[u][i];
            if(v==pre)continue;
            dfs(v,u);ol[++ont]=u;
        }
        ed[u]=dnt;
    }
    
    void rmqpre(){
        lg[1]=0;rmq[1][0]=ol[1];
        for(int i=2;i<=ont;i++)rmq[i][0]=ol[i],lg[i]=lg[i>>1]+1;
        for(int j=1;j<=18;j++)
        for(int i=1;i+(1<<j)<=ont;i++){
            rmq[i][j]=rmq[i][j-1];
            if(dep[rmq[i+(1<<(j-1))][j-1]]<dep[rmq[i][j]])
            rmq[i][j]=rmq[i+(1<<(j-1))][j-1];
        }
    }
    
    
    
    int lca(int x,int y){
        x=fir[x];y=fir[y];if(x>y)swap(x,y);int t=lg[y-x+1];
        return dep[rmq[x][t]]<dep[rmq[y-(1<<t)+1][t]]?rmq[x][t]:rmq[y-(1<<t)+1][t];
    }
    int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
    
    node merge(node a,node b){
        if(a.len==-1)return b;
        int x1=a.x,x2=b.x;
        int y1=a.y,y2=b.y;
        node t=a.len>b.len?a:b;
        if(dis(x1,x2)>t.len)t=(node){x1,x2,dis(x1,x2)};
        if(dis(x1,y2)>t.len)t=(node){x1,y2,dis(x1,y2)};
        if(dis(y1,x2)>t.len)t=(node){y1,x2,dis(y1,x2)};
        if(dis(y1,y2)>t.len)t=(node){y1,y2,dis(y1,y2)};
        return t;
    }
    
    void build(int u,int l,int r){
        if(l==r){
            t[u].x=t[u].y=dfn[l];
            t[u].len=0;return;
        }
        int mid=(l+r)>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
        t[u]=merge(t[u<<1],t[u<<1|1]);
    }
    
    node query(int u,int l,int r,int x,int y){
        if(x<=l&&r<=y)return t[u];
        int mid=(l+r)>>1;
        node t=(node){0,0,-1};
        if(x<=mid)t=merge(t,query(u<<1,l,mid,x,y));
        if(y>mid)t=merge(t,query(u<<1|1,mid+1,r,x,y));
        return t;
    }
    
    int main(){
    //    freopen("1.in","r",stdin);
      //  freopen("wa.out","w",stdout);
        scanf("%d%d",&n,&q);
        for(int i=1;i<n;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            s[u].push_back(v);
            s[v].push_back(u);
        }
        dfs(1,0);rmqpre();
        build(1,1,dnt);
        for(int i=1;i<=q;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            if(st[x]>st[y])swap(x,y);
            node ans=(node){0,0,-1};
            if(st[x]>1)ans=merge(ans,query(1,1,dnt,1,st[x]-1));
            if(ed[x]+1<st[y])ans=merge(ans,query(1,1,dnt,ed[x]+1,st[y]-1));
            int end=max(ed[x],ed[y]);
            if(end<dnt)ans=merge(ans,query(1,1,dnt,end+1,dnt));
            printf("%d
    ",ans.len==-1?0:ans.len);
        }
        
        return 0;
    }
    View Code

      这种题就很皮了。

     

      3.树上倍增找儿子

      这种题见得到是不多啊,但是noip见过一道,开车旅行的什么,烦的一匹,这种东西建边还要卡你,逼着你用nlogn的stl或数结构,非常的皮。

      有一篇写的很好的博客,贴在这里,只不过他的代码很丑陋。

      http://www.cnblogs.com/Paul-Guderian/p/6853123.html

      哎这个人就把倍增的精髓领会到了,不错不错。

      4.序列诡异倍增

      http://www.cnblogs.com/oijzh/archive/2012/08/20/2648023.html

      这个就很皮了。

     

      三、倍增法求后缀数组sa[]

      哇这个讲起来不是一般滴麻烦,提一下表示自己知道。

      还经常和RMQ混考。代码也懒得贴了,不是重点。

     

      ●例题

      https://vijos.org/p/1843

      Noip2013的货车运输,值得一做,它把RMQ算法求最小距离也混进了lca里面,还不错。

      顺便也贴了代码

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #define N 50005
    using namespace std;
    int n,m,q,fa[N],hd[N],p[N][25],d[N],rmq[N][25],cnt=1,tot;
    int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}struct edge{int v,next,w;}e[N]; 
    struct edge1{int u,v,w;bool operator <(const edge1 &b)const{return w>b.w;}}e1[N];
    
    void dfs(int u,int dad,int val){
        p[u][0]=dad;
        d[u]=d[dad]+1;
        rmq[u][0]=val;
        for(int i=1;(1<<i)<=d[u];i++){
            p[u][i]=p[p[u][i-1]][i-1];
            rmq[u][i]=min(rmq[u][i-1],rmq[p[u][i-1]][i-1]);
        }
        for(int i=hd[u];i;i=e[i].next)
        if(e[i].v!=dad)dfs(e[i].v,u,e[i].w);    
    }
    
    int lca(int x,int y){
        int ans=99999999;
        if(d[x]>d[y])swap(x,y);
        for(int i=20;~i;i--)
        if(d[x]<=d[y]-(1<<i))
        {ans=min(ans,rmq[y][i]);y=p[y][i];}
        if(x==y)return ans;
        for(int i=20;~i;i--){
            if(p[x][i]==p[y][i])continue;
            ans=min(rmq[x][i],ans);
            ans=min(rmq[y][i],ans);
            x=p[x][i];y=p[y][i];
        }
        return min(ans,min(rmq[x][0],rmq[y][0]));
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            e1[i]=(edge1){u,v,w};
        }
        for(int i=1;i<=n;i++)fa[i]=i;
        sort(e1+1,e1+1+m);
        for(int i=1;i<=m;i++){
            int u=find(e1[i].u),v=find(e1[i].v);
            if(fa[u]==fa[v])continue;fa[v]=u;
            e[++tot]=(edge){e1[i].v,hd[e1[i].u],e1[i].w};hd[e1[i].u]=tot;
            e[++tot]=(edge){e1[i].u,hd[e1[i].v],e1[i].w};hd[e1[i].v]=tot;
            cnt++;if(cnt==n)break;
        }
        dfs(1,0,0);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            int u=find(x),v=find(y);
            if(u!=v)puts("-1");
            else printf("%d
    ",lca(x,y));
        }
        return 0;
    }
    View Code

      http://codeforces.com/problemset/problem/733/F

      题解:http://www.cnblogs.com/MashiroSky/p/6027250.html

     

      其他的题就不拿出来水了,都是很裸的。

     

      ●总结

      再提一句,倍增算法其实是比较裸的了,很容易看出来。

      先看数据范围吧,点数是100000附加100000组询问,估计就是nlogn预处理加查询咯。

      再看步数吧。那个10^18的步数不明显是要倍增做吗?

      其实倍增没什么难得,代码都可以强行记住。只要学会灵活运用,就不会怕倍增题目了。。

       

     

      好吧,最后的总结其实是:注意long long!

     

                            (完)

     

     

  • 相关阅读:
    (hash) leetcode 49. Group Anagrams
    (Manacher Algorithm, 中心拓展法,动态规划) leetcode 5. 最长回文串
    (KMP) leetcode 28. Implement strStr() 字符串匹配
    (字符串,哈希) leetcode 8. atoi,12. Integer to Roman
    (贪心) leetcode 435 Non-overlapping Intervals, 455 design cookies
    (Divide and Conquer) Closest Pair of Pairs
    (栈,双端队列,双指针) leetcode. 844 Backspace String Compare
    write、writelines
    *、**
    inf
  • 原文地址:https://www.cnblogs.com/wsy01/p/7571386.html
Copyright © 2011-2022 走看看