莫队算法 区间Gcd Hdu 5381

来源:互联网 发布:淘宝联盟和淘客联盟 编辑:程序博客网 时间:2024/05/20 19:18

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
251 2 3 4 531 32 31 444 2 6 931 32 42 3
 

Sample Output
9616182310
 
题意:给出一个数列,有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;}