zoukankan      html  css  js  c++  java
  • 倍增的奇妙用处

    倍增的奇妙用处


    前言:

      这里并不打算教你什么是倍增,只是倍增奇妙的一些应用QwQ


    应用:

      1.倍增求树上LCA:

      洛谷P1967货车运输

      题目概述:给定x,y,求从x到y最多运多少货物。

      经典题目了,先跑一遍最大生成树,然后树上倍增处理出路上最小载重。秒了

      2.矩阵快速幂:

      洛谷P4159

      题目概述:有N个点的有向图,数条有边权的有向边,求恰好在T时刻到达N点的方案数。(1<=边权<=9)

      分析:如果没有边权就是裸题了,众所周知邻接矩阵的k次方就是k步到达某个点的方案。

      边权不大,只要把每个点拆成9个点,分别代表边权长,每次目标点连到1号,出发点连到相应节点,再每个点的1到9再连上边就可以跑矩阵快速幂了。

      矩阵满足结合律可以快速幂

      

      3.倍增加速转移:

      对于目的地距离明确,边界限制明确的移动,通常可以倍增加速转移(莽过去)。

      洛谷P3509

      题目概述:数轴上有n个点,有一只青蛙在上面跳,每次跳到第k小的点(距离相同向下标较小的点跳),求从每个点出发跳m次后在哪个点。

      分析:显然只要处理出每个点下一次跳到哪里就可以开始倍增了qwq,那怎么处理下一次跳到哪里呢(?)

      可以发现对于一个点,它的k小点一定分布在它的周围,而且随着点的移动分布范围单调移动,显然是一个尺取法的模型

    int l=1,r=m+1;
        f[1][0]=r;
        for(int i=2;i<=n;++i)
        {
            while(r<n&&a[i]-a[l]>a[r+1]-a[i]) ++l,++r;
            f[i][0]=(a[r]-a[i]>a[i]-a[l]?r:l);
        }

      然后每个点倍增暴跳就可以了

      4.倍增解RMQ问题

      倍增解RMQ包括熟知的ST表,这里放一个ST的扩展。

      洛谷P2048超级钢琴

      题目概述:在长度为 l 到 r 的所有区间中选出区间和最大的m个

      分析:区间问题先维护一个前缀和总没错qwq

      维护了前缀和我们发现,如果固定一个左端点s,那么右端点 t 的取值范围是固定的,区间和为 sum[ t ] - sum [ s-1 ] ;

      当s固定之后,sum [ s-1 ] 就成为了定值,现在找区间和最大就变成了在某个区间内找sum [ t ]最大的问题 。

      st表qwq;

      每次选取一个区间之后,我们要把这个区间右端点的左侧和右侧重新利用起来,所以我们应该在找最大值的时候记录右端点的位置,对st表进行魔改。

      

    inline int rmq(int l,int r)//查询 
    {
        int maxans=-2e9,pos;
        for(int d=19;d>=0;--d)
        {
            if(l+(1<<d)-1<=r)
            {
                if(mx[l][d]>maxans)
                {
                    maxans=mx[l][d];
                    pos=id[l][d];
                }
                l=f[l][d]+1;
            }
        }
        return pos;
    }
    for(int i=1;i<=19;++i)//预处理 
    {
        for(int j=1;j<=n;++j)
        {
            f[j][i]=f[f[j][i-1]+1][i-1];
            if(mx[j][i-1]>mx[f[j][i-1]+1][i-1])
            {
                mx[j][i]=mx[j][i-1];
                id[j][i]=id[j][i-1];
            }
            else
            {
                mx[j][i]=mx[f[j][i-1]+1][i-1];
                id[j][i]=id[f[j][i-1]+1][i-1];
            }
        }
    }

      那么怎么在众多区间中选最大值呢?当然是堆啦,每次把一个区间的信息扔进堆里

      信息包括:区间左端点,右端点,右端点左右边界;

      AC代码:

      

    #include<bits/stdc++.h>
    using namespace std;
    inline int read()
    {
        int x=0,f=1;
        char ch;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x; 
    }
    int n,m,l,r;
    long long res;
    int a[550010];
    int sum[550010];
    int mx[550010][21];
    int id[550010][21];
    int f[550010][21];
    struct point
    {
        int s,l,r,t;
        bool operator <(point y)const
        {
            return sum[t]-sum[s-1]<sum[y.t]-sum[y.s-1];
        }
    }miao,wu;
    priority_queue<point> q;
    inline int rmq(int l,int r)
    {
        int maxans=-2e9,pos;
        for(int d=19;d>=0;--d)
        {
            if(l+(1<<d)-1<=r)
            {
                if(mx[l][d]>maxans)
                {
                    maxans=mx[l][d];
                    pos=id[l][d];
                }
                l=f[l][d]+1;
            }
        }
        return pos;
    }
    signed main()
    {
        n=read(),m=read(),l=read(),r=read();
        for(int i=1;i<=n;++i)
        {
            a[i]=read();
            sum[i]=sum[i-1]+a[i];
            mx[i][0]=sum[i];
            id[i][0]=i;
            f[i][0]=i;
        }
        for(int i=1;i<=19;++i)
        {
            for(int j=1;j<=n;++j)
            {
                f[j][i]=f[f[j][i-1]+1][i-1];
                if(mx[j][i-1]>mx[f[j][i-1]+1][i-1])
                {
                    mx[j][i]=mx[j][i-1];
                    id[j][i]=id[j][i-1];
                }
                else
                {
                    mx[j][i]=mx[f[j][i-1]+1][i-1];
                    id[j][i]=id[f[j][i-1]+1][i-1];
                }
            }
        }
        for(int i=1;i<=n;++i)
        {
            if(i+l-1>n) break;
            miao.s=i;
            miao.l=i+l-1;
            miao.r=min(i+r-1,n);
            miao.t=rmq(miao.l,miao.r);
            q.push(miao);
        }
        while(m--)
        {
            miao=q.top();
            q.pop();
            res+=sum[miao.t]-sum[miao.s-1];
            if(miao.t>miao.l)
            {
                wu.s=miao.s;
                wu.l=miao.l;
                wu.r=miao.t-1;
                wu.t=rmq(wu.l,wu.r);
                q.push(wu);
            }
            if(miao.t<miao.r)
            {
                wu.s=miao.s;
                wu.l=miao.t+1;
                wu.r=miao.r;
                wu.t=rmq(wu.l,wu.r);
                q.push(wu);
            }
        }
        printf("%lld
    ",res);
    return 0;
    }

      5.其他奇怪的暴搞方式

      CF875D

      题目概述:给一个序列,求有多少区间满足区间或>区间最大值。

      分析:我们可以转化为求有多少区间不满足区间或>区间最大值问题。

      对于每一个数字,求出它的控制范围(在哪个区间内是最大值),这个可以用单调栈O(n)求出来。

      每个控制范围被最大值分为左右两侧,我们可以对较小的一侧暴力遍历,对另一侧倍增地跳

      复杂度?复杂度无法保证,大概是O(能过)

      

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    inline int read()
    {
        int x=0,f=1;
        char ch;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x;
    }
    int n,res;
    int a[500010],l[500010],r[500010];
    int fl[500010][21];
    int fr[500010][21];
    int st[500010],top;
    signed main()
    {
        n=read();
        a[0]=a[n+1]=1e9+7;
        for(int i=1;i<=n;++i)
        {
            a[i]=read();
            fl[i][0]=fr[i][0]=a[i];
            while(a[i]>=a[st[top]]&&top) --top;
            l[i]=st[top]+1;
            st[++top]=i;
        }
        top=0;
        st[top]=n+1;
        for(int i=n;i>=1;--i)
        {
            /*注意一侧开区间一侧闭区间*/ 
            while(a[i]>a[st[top]]&&top) --top;
            r[i]=st[top]-1;
            st[++top]=i;
        }
        for(int i=1;i<=18;++i)
        {
            for(int j=1;j<=n;++j)
            {
                fl[j][i]=fl[j][i-1]|fl[j+(1<<(i-1))][i-1];
            }
            for(int j=n;j>=1;--j)
            {
                fr[j][i]=fr[j][i-1];
                if(j>=(1<<(i-1)))
                    fr[j][i]=fr[j][i-1]|fr[j-(1<<(i-1))][i-1];
            }
        }
        for(int i=1;i<=n;++i)
        {
            if(i-l[i]<=r[i]-i)
            {
                for(int k=l[i];k<=i;++k)
                {
                    int now=k;
                    int sum=a[k];
                    for(int d=18;d>=0;--d)
                    {
                        if(now+(1<<d)<=r[i]&&(sum|fl[now+1][d])<=a[i])
                        {
                            sum |= fl[now+1][d];
                            now+=(1<<d);
                        }
                    }
                    if(now>=i) res+=(now-i+1);
                }
            }
            else
            {
                for(int k=i;k<=r[i];++k)
                {
                    int now=k;
                    int sum=a[k];
                    for(int d=18;d>=0;--d)
                    {
                        if(now-(1<<d)>=l[i]&&(sum|fr[now-1][d])<=a[i])
                        {
                            sum |= fr[now-1][d];
                            now-=(1<<d);
                        }
                    }
                    if(now<=i) res+=(i-now+1);
                }
            }
        }
        printf("%lld
    ",(n)*(n+1)/2-res);
    return 0;
    }
  • 相关阅读:
    面向对象类成员之静态字段
    面向对象中,用super来联系父类的函数
    登录
    奇数偶数
    vue中播放音乐
    vue中的轮播图
    vue中的bind
    vue中的for
    django rest_framework中的APIView,ModelViewSet,认证,权限,频率,版本
    django rest_framework中的mixins,generics,ModelViewSet中的url简写
  • 原文地址:https://www.cnblogs.com/knife-rose/p/11255240.html
Copyright © 2011-2022 走看看