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
Let f(l,r)=∑ri=l∑rj=igcd(ai,ai+1....aj)
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
1≤T≤3
1≤n,Q≤104
1≤ai≤109
1≤l<r≤n
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
9 6 16 18 23 10
所谓 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;
}