hdu 5381 The sum of gcd 2015多校联合训练赛#8莫队算法

The sum of gcd

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 23    Accepted Submission(s): 4


Problem Description
You have an array  A ,the length of  A  is  n
Let  f(l,r)=ri=lrj=igcd(ai,ai+1....aj)
 

Input
There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:
First line has one integers  n
Second line has  n  integers  Ai
Third line has one integers  Q ,the number of questions
Next there are Q lines,each line has two integers  l , r
1T3
1n,Q104
1ai109
1l<rn
 

Output
For each question,you need to print  f(l,r)
 

Sample Input
  
  
2 5 1 2 3 4 5 3 1 3 2 3 1 4 4 4 2 6 9 3 1 3 2 4 2 3
 

Sample Output
  
  
9 6 16 18 23 10
 

Source

求一个区间内任意子区间的gcd之和。

分析:

对于区间[l,r]求gcd之和的复杂度是nlog(n)的

::

      假设处理了[l,r]的结果,那么对于[l,r+1],能产生的新的子区间为[r-l+1]个。如何合并?

      因为加入r+1,那么[L,r+1],(l<=L<=r)必然都是经过r位置的。知道r与之前每个位置的gcd。

用num[r+1]与这些gcd值,做gcd得到新的gcd值,就是所有新子区间的gcd结果,对于每个gcd乘以对应的

区间个数即可。

       当然越往左,gcd就会越小,并且最多出现log个gcd值,把相同的gcd合并就能减少运算量。

然后新加入的数自己可以成为一个区间,加入答案中。处理的复杂度是nlog的,因为分块了,

长度为n的最多sqrt(n)段,复杂度是nlog(n)*sqrt(n)

在块内,长度是sqrt(n)且最多次计算,所有是qlog(n)*sqrt(n)==================(log(n)是gcd的种类数)


现在处理合并两段了:

         因为分成左右两段,如下

原序列:1 1 1 2 2  2  4 4 4| 4 4 4 2 2 2 1 1 1 (|是分隔位置)

     gcd:1 1 1  2 2 2  4 4 4 | 4 4 4 2 2 2 1 1 1

gcd计算的该点到分割位置的路径的gcd,因为合并肯定是需要经过分割位置的!

       显然可以知道gcd的种类只有 log(n)个。对于左边的每个gcd和右边的每个gcd做一下gcd函数。然后乘以左边该段

的长度*右边该段的长度。如例子就是

            gcd(1,1)*3*3+gcd(1,2)*3*3 + gcd(1,4)*3*3)...........+....

如何计算得到每个gcd对应的区间个数呢?

             可以知道从分隔线到两边的gcd是递减的。如果g是[l,r]的gcd,那么对于[l-1,r]只要gcd(g,num[l-1])就计算出来了

            然后得到的gcd如果与g相同就和并,否则加入一个新值。

          复杂度分析:

              对于每段,求出所有gcd和个数是o(n)的。长的一段最多求sqrt(n)次,是n*sqrt(n)的。短的是sqrt(n)*q次

合并时复杂度是q*log(n)*log(n)的,所以是 O(sqrt(n)*n+q*sqrt(n)+q*log(n)*log(n))

接下来看代码:




#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 20001
#define ll long long

int gcd(int a,int b){
    if(b == 0) return a;
    return gcd(b,a%b);
}

ll ans[maxn];
struct Node{
    int l,r,id;
};
Node que[maxn];

int length;//分块排序函数
int comp(Node a,Node b){
    if(a.l / length == b.l / length)
        return a.r < b.r;
    return a.l /length < b.l/length;
}
int num[maxn];

struct Point{
    int g,num;
    Point(int _g=0,int _n=0):g(_g),num(_n){}
};
vector<Point> lgcd;
vector<Point> ltrgcd;
vector<Point> rgcd;
vector<Point> rtlgcd;

int main(){
    int t,n,q;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i = 0;i < n; i++)
            scanf("%d",num+i);
        scanf("%d",&q);
        for(int i = 0;i < q; i++){
            scanf("%d%d",&que[i].l,&que[i].r);
            que[i].id = i;
            que[i].l--,que[i].r--;
        }

        for(length = 1; length * length < n; length++);
        sort(que,que+q,comp);//按快排序,同一个块内r从小到大排序

        memset(ans,0,sizeof(ans));
        lgcd.clear();
        rgcd.clear();
        ltrgcd.clear();
        rtlgcd.clear();

        int RR = -1,kuai = -1,j,k,l,LL;
        ll res = 0,resl = 0;
        Point a;
        for(int i = 0;i < q; i++){ 处理每个询问
            if(kuai != que[i].l/length){//新块,所以长的一段要重头开始处理
                kuai = que[i].l/length;
                rtlgcd.clear();
                rgcd.clear();
                RR = kuai*length+length-1;
                res = 0;
            }
            while(RR < que[i].r){
                RR++;//处理分隔线到RR的gcd之和。
                for( j = 0;j < rgcd.size(); j++){
                    rgcd[j].g = gcd(rgcd[j].g,num[RR]);
                    res += (ll)rgcd[j].g*rgcd[j].num;
                }
                rgcd.push_back(Point(num[RR],1));
                res += num[RR];
                for(j = 0,k = 1;k<rgcd.size();k++){
                    if(rgcd[j].g == rgcd[k].g){
                        rgcd[j].num += rgcd[k].num;
                    }
                    else {
                        j++;
                        rgcd[j] = rgcd[k];
                    }
                }
                while(rgcd.size() > j+1) rgcd.pop_back();//合并相同的gcd
                 //处理分隔线到每个RR的gcd个数
                if(rtlgcd.size() == 0)
                    rtlgcd.push_back(Point(num[RR],1));
                else {
                    k = rtlgcd.size()-1;//只需比较最小的那个gcd即可,即最右边计算得到的gcd
                    a.g = gcd(rtlgcd[k].g,num[RR]);
                    a.num = 1;
                    if(a.g == rtlgcd[k].g)
                        rtlgcd[k].num++;
                    else rtlgcd.push_back(a);
                }
            }

            LL = kuai*length+length-1;
            lgcd.clear();
            ltrgcd.clear();
            resl = 0;//左边的处理与右边的一样
            LL = min(LL,que[i].r);
            for(;LL >= que[i].l; LL--){
                for(j = 0;j < lgcd.size(); j++){
                    lgcd[j].g = gcd(lgcd[j].g,num[LL]);
                    resl += (ll)lgcd[j].g*lgcd[j].num;
                }
                lgcd.push_back(Point(num[LL],1));
                resl += num[LL];
                for(j = 0,k=1;k<lgcd.size();k++){
                    if(lgcd[j].g == lgcd[k].g){
                        lgcd[j].num += lgcd[k].num;
                    }
                    else {
                        j++;
                        lgcd[j] = lgcd[k];
                    }
                }
                while(lgcd.size() > j+1) lgcd.pop_back();

                if(ltrgcd.size() == 0){
                    ltrgcd.push_back(Point(num[LL],1));
                }
                else {
                    k = ltrgcd.size()-1;
                    a.g = gcd(ltrgcd[k].g,num[LL]);
                    a.num = 1;
                    if(a.g == ltrgcd[k].g)
                        ltrgcd[k].num++;
                    else ltrgcd.push_back(a);
                }
            }//合并两个区间
            ans[que[i].id] = res + resl;
            int id = que[i].id,gg;
            for(j = 0;j < ltrgcd.size(); j++){
                gg = ltrgcd[j].g;
                for(k = 0;k < rtlgcd.size(); k++){
                    gg = gcd(gg,rtlgcd[k].g);
                    ans[id] += (ll)gg*ltrgcd[j].num*rtlgcd[k].num;
                }
            }
        }
        for(int i = 0;i < q; i++){
            printf("%I64d\n",ans[i]);
        }
    }
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GDRetop

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值