莫队算法 区间Gcd Hdu 5381

The sum of gcd

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


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
 
题意:给出一个数列,有m次询问,每次询问给出一个区间段的下标,问你这个区间里的 f(l,r)值为多少

所谓 f(l,r) 就比如

 f(1,3) =gcd(a1,a1) + gcd(a1,a2) + gcd(a1,a2,a3)

         + gcd(a2,a2) + gcd(a2,a3)

         + gcd(a3,a3)

就相当于 (a1,a2,a3) 然后枚举每个数,向后尺取gcd

思路:依然是按照莫队的 sqrt(n)分块,排序询问,然后移动指针找答案

1.先打印好前缀的 lgcd 和 后缀 rgcd ,我使用动态数组存的,节省很多空间

但是里面有一个巧妙的地方是,不把所有的前缀都存进去,只存gcd值不一样的,且记录下第一个不一样的gcd的下标。这样的话,你取了一个gcd之后,下一个gcd值的个数可以通过前一个的下标和当前下标相减求得,也是节省了时间的。 前缀后缀都这么记录

2.开始移动指针,当右指针右移的时候,表示向右加数,对于加的那个数的每个gcd贡献,去与左极限对比找出贡献个数,然后加上。,右指针左移则是减贡献。移动好右指针后移动左指针,操作相同,不过左指针是与右极限对比找该gcd值的贡献个数。

做个模拟比较易懂

5     [1, 2 ,3 ,4 ,5]  询问 [ 1,3 ]

左指针 l = 1, 右指针 r = 0

1.右指针向右 移1, rgcd[1] 里只有 (1,1) (ps:表示贡献gcd值为1,下标为1) Ans += 1   Ans = 1

2.右指针向右 移1, rgcd[2] 里右 (2,2) (1,1) (ps:因为 gcd(1,2) = 1,gcd为1的下标开始为1) 

(2,2) Ans += 2  , (1,1) Ans += 1 * (2 - 1)   Ans = 4;

3.右指针向右移1 , rgcd[3] 里有 (3,3) (1,1)  

(3,3) Ans += 3   

(1,1) Ans += 1 * (3 - 1) (ps:开始下标为1,现在为3,那么中间就是有两个贡献为1的,分别是 (1,3) 和(2,3) )Ans = 9

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define maxn 10004
#define mem(a,x) memset(a,x,sizeof(a))
#define ll long long 

struct node{
    int l,r,id;
}q[maxn];
typedef pair<int,int>P;
vector<P>lgcd[maxn],rgcd[maxn];

int T,n,m,l,r;
int pos[maxn],a[maxn];
ll ans[maxn];
ll Ans = 0;

bool cmp(node a,node b){
    if(pos[a.l] != pos[b.l])
        return pos[a.l] < pos[b.l];
    if(pos[a.r] != pos[b.r])
        return pos[a.r] < pos[b.r];
    return a.l < b.l;
}
void addl(int x){
    int last = l;
    ll tmp = 0;
    for(int i = 0;i < lgcd[l].size();i++){ // 找到它的gcd贡献 
        int g = lgcd[l][i].first,p = lgcd[l][i].second;//获得这个gcd值的 开始下标
        if(p <= r){
            tmp += 1ll * (p - last + 1) * g; // (p - last + 1)表示有多少个连续相同的gcd值,一起算完 
            last = p + 1;
        }else{
            tmp += 1ll * (r - last + 1) * g;
            break;
        }
    }
    Ans += 1ll * x * tmp; // x = -1 表示减 1 则为加 
}
void addr(int x){
    int last = r;
    ll tmp = 0;
    for(int i = 0;i < rgcd[r].size();i++){
        int g = rgcd[r][i].first,p = rgcd[r][i].second; //获得这个gcd值的 结束下标 
        if(p >= l){
            tmp += 1ll * (last - p + 1) * g;// (last - p + 1)表示有多少个连续相同的gcd值,一起算完
            last = p - 1;
        }else{
            tmp += 1ll * (last - l + 1) * g;
            break;
        }
    }
    Ans += 1ll * x * tmp;
}
void init(){
    for(int i = 1;i <= n;i++){	// rgcd[x] 存以x为结尾的连续gcd 
        int x = a[i],y = i;
        for(int j = 0;j < rgcd[i - 1].size();j++){
            int g = __gcd(rgcd[i - 1][j].first,a[i]);
            if(g != x){
                rgcd[i].push_back(P(x,y));
                x = g;
            }
            y = rgcd[i - 1][j].second;
        }
        rgcd[i].push_back(P(x,y));
    }
    for(int i = n;i >= 1;i--){	// lgcd[x] 存以x开头的连续gcd 
        int x = a[i],y = i;
        for(int j = 0;j < lgcd[i + 1].size();j++){
            int g = __gcd(lgcd[i + 1][j].first,a[i]);
            if(g != x){
                lgcd[i].push_back(P(x,y)); // 不会全部存进去,连续相同的会跳过 
                x = g;
            }
            y = lgcd[i + 1][j].second;
        }
        lgcd[i].push_back(P(x,y));
    }    
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        int block = sqrt(n);
        for(int i = 1;i <= n;i++){
            scanf("%d",&a[i]);
            pos[i] = i / block;
            rgcd[i].clear();
            lgcd[i].clear();
        }
        rgcd[0].clear();
        lgcd[0].clear();
        init();	// 打好前缀和后缀 
        scanf("%d",&m);
        for(int i = 1;i <= m;i++){
            scanf("%d %d",&q[i].l,&q[i].r);
            q[i].id = i;
        }
        sort(q + 1,q + 1 + m,cmp);	//分块排序 
        Ans = 0;
        l = 1,r = 0;
        for(int i = 1;i <= m;i++){ //移动找答案 
            while(r < q[i].r){
                r++;
                addr(1);
            }
            while(r > q[i].r){
                addr(-1);
                r--;
            }
            while(l < q[i].l){
                addl(-1);
                l++;
            }
            while(l > q[i].l){
                l--;
                addl(1);
            }
            ans[q[i].id] = Ans;
        } 
        for(int i = 1;i <= m;i++){
            printf("%lld\n",ans[i]);
        }
    }
    return 0;
} 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值