zoukankan      html  css  js  c++  java
  • HDU5381【莫队算法+区间GCD特性】

    前言:

    主要最近在刷莫队的题,这题GCD的特性让我对莫队的使用也有了新的想法。给福利:神犇的一套莫队算法题

    先撇开题目,光说裸的一个莫队算法,主要的复杂度就是n*sqrt(n)对吧,这里我忽略了一个左端点(增加/删除)或者右端点(增加/删除)的所带来的复杂度,
    之前也遇到过卡这里的复杂度,但是是因为简单的long long计算多而造成了复杂度增大,从而转变一下。

    回到这道题:给出区间,求所有子区间的gcd和。

    思路:
    莫队算法+gcd的特性。
    外面就是套了一个莫队,排序然后离散化操作优化了复杂度得n*sqrt(n)。
    然后呢?我们要去计算一个右结点的增加或删除的贡献。
    先预处理所有区间之间的gcd,利用ST表。

    在这里:当一个右端点的删除/增加
    问题就是:如何快速求所有存在这个右端的子区间的GCD的贡献。

    这里利用的是区间gcd的特性,一段区间上不同的gcd最多只有logn个。

    对于右端点:

    预处理右端点固定,不同gcd的区间段的GCD(直接枚举,更新位置,由于一段区间GCD的gcd个数不会超过logn,所以最多会预处理logn段不同的gcd段),并且预处理右端固定的不同gcd段的最远位置(用二分就行,因为GCD会随着区间大而变小,当前区间最小即最大),所以每次查询时间要乘以logn;

    对于左端点同理;

    总的复杂度:O(n*sqrt(n)*log(n))。
    代码还有一些简要注释可以参考。

    这个代码打不出来...就多打打!练手!23333333333

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    typedef pair<int,int> PII;
    const int N=1e4+10;
    int a[N],n,m;
    vector<PII>VL[N];
    vector<PII>VR[N];
    int pos[N];
    struct asd{
        int left,right,id;
        LL res;
    };
    asd S[N];
    bool cmp(asd x,asd y)
    {
        if(pos[x.left]==pos[y.left]) return x.right<y.right;
        return pos[x.left]<pos[y.left];
    }
    
    //RMQ预处理,以某个端点为起点向一个方向延伸的区间的gcd的最远延伸的方向和对应的gcd
    //Rmq[i][j]表示第 i 个数起,连续 2^j 个数的GCD;
    int Rmq[N][15];
    void GetRmq()
    {
        for(int i=1; i<=n; i++)
            Rmq[i][0]=a[i];
        for(int i=1; (1<<i)<=n; i++)
            for(int j=1; j<=n; j++)
                if(j+(1<<i)-1<=n)
                    Rmq[j][i]=__gcd(Rmq[j][i-1],Rmq[j+(1<<(i-1))][i-1]);
    }
    int query(int L, int R)
    {
        int k=(int)log2(R-L+1);
        return __gcd(Rmq[L][k],Rmq[R-(1<<k)+1][k]);
    }
    //固定s为右端点,向左延伸gcd为t的最远位置
    int Rsearch(int s,int L,int R,int t)
    {
        int ans;
        while(L<=R)
        {
            int mid=(L+R)>>1;
            if(query(mid,s)==t)
            {
                ans=mid;
                R=mid-1;
            }
            else L=mid+1;
        }
        return ans;
    }
    //固定s为左端点,向右延伸gcd为t的最远位置
    int Lsearch(int s,int L,int R,int t)
    {
        int ans;
        while(L<=R)
        {
            int mid=(L+R)>>1;
            if(query(s,mid)==t)
            {
                ans=mid;
                L=mid+1;
            }
            else R=mid-1;
        }
        return ans;
    }
    //计算s为右端点的贡献,t 为当前区间左端点
    LL Rcal(int s,int t)
    {
        LL ans=0;
        int ss=s;
        for(int i=0;i<VR[s].size();i++)
        {
            ans+=(1LL*(ss-max(t,VR[s][i].second)+1)*VR[s][i].first);
            ss=VR[s][i].second-1;
            if(ss<t) break;
        }
        return ans;
    }
    //计算s为左端点的贡献,t 为当前区间右端点
    LL Lcal(int s,int t)
    {
        LL ans=0;
        int ss=s;
        for(int i=0;i<VL[s].size();i++)
        {
            ans+=(1LL*(min(t,VL[s][i].second)-ss+1)*VL[s][i].first);
            ss=VL[s][i].second+1;
            if(ss>t) break;
        }
        return ans;
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n);
            int block=(int)sqrt(n);
            for(int i=1; i<=n; i++)
            {
                scanf("%d",&a[i]);
                pos[i]=(i-1)/block+1;
            }
            GetRmq();
    
            //预处理左端 i 固定的不同gcd区间段
            for(int i=1;i<=n;i++)
            {
                int r=i;
                VL[i].clear();
                while(r<=n)
                {
                    int ant=query(i,r);
                    r=Lsearch(i,r,n,ant);
                    VL[i].push_back(make_pair(ant,r));
                    r++;
                }
            }
             //预处理右端 i 固定的不同gcd区间段
            for(int i=n;i>=1;i--)
            {
                int l=i;
                VR[i].clear();
                while(l>=1)
                {
                    int ant=query(l,i);
                    l=Rsearch(i,1,l,ant);
                    VR[i].push_back(make_pair(ant,l));
                    l--;
                }
            }
            scanf("%d",&m);
            for(int i=0;i<m;i++)
            {
                scanf("%d%d",&S[i].left,&S[i].right);
                S[i].id=i;
            }
            sort(S,S+m,cmp);
            LL sum=0;
            int l=0,r=1;
            for(int i=0;i<m;i++)
            {
                while(r<=S[i].right)
                {
                    sum+=Rcal(r,l+1);
                    r++;
                }
                while(r>S[i].right+1)
                {
                    r--;
                    sum-=Rcal(r,l+1);
                }
                while(l<S[i].left-1)
                {
                    l++;
                    sum-=Lcal(l,r-1);
                }
                while(l>=S[i].left)
                {
                    sum+=Lcal(l,r-1);
                    l--;
                }
                S[S[i].id].res=sum;
            }
            for(int i=0;i<m;i++)
                printf("%lld
    ",S[i].res);
        }
        return 0;
    }
    



  • 相关阅读:
    最权威的 Android Oreo 新特性详解
    【送书福利】第一次送书活动(总共10本)
    【资源篇】Python那么火,你还不知道如何人门?
    不忘初心,方得始终 ,让我们一起学习成长,感谢有你!
    搭建环境篇 | 运行第一个Java Web 项目
    为什么我们需要看技术公众号文章?
    手把手教学APK反编译实现源码阅读
    分享一款 Google Pixel 2 独家动态壁纸
    了解CoordinatorLayout,在项目中运用
    jdk 与 jre的区别
  • 原文地址:https://www.cnblogs.com/keyboarder-zsq/p/6777414.html
Copyright © 2011-2022 走看看