Different GCD Subarray Query HDU - 5869
This is a simple problem. The teacher gives Bob a list of problems about GCD (Greatest Common Divisor). After studying some of them, Bob thinks that GCD is so interesting. One day, he comes up with a new problem about GCD. Easy as it looks, Bob cannot figure it out himself. Now he turns to you for help, and here is the problem:
Given an array a of N positive integers a1,a2,⋯aN−1,aN; a subarray of a is defined as a continuous interval between a1 and aN. In other words, ai,ai+1,⋯,aj−1,aj is a subarray of a, for 1≤i≤j≤N. For a query in the form (L,R), tell the number of different GCDs contributed by all subarrays of the interval [L,R]
.
Input
There are several tests, process till the end of input.
For each test, the first line consists of two integers N and Q, denoting the length of the array and the number of queries, respectively. N positive integers are listed in the second line, followed by Q lines each containing two integers L,R for a query.
You can assume that
1≤N,Q≤100000
1≤ai≤1000000
Output
For each query, output the answer in one line.
Sample Input
5 3
1 3 4 6 9
3 5
2 5
1 5
Sample Output
6
6
6
题意:
长度n的序列, m个询问区间[L, R], 问区间内的所有子段的不同GCD值有多少种.
分析:
首先需要先说明下gcd的性质
假设我们固定右端点(注意后面我们都是固定右端点),区间向左延伸,那么gcd个数一定是呈现非递增性质的,即要么不变要么减小,并且减小的话一定减少一半,因为实际上一段区间内,不同的gcd的个数,不会超过log个
那么假设我们已经求出了区间[1,r]的不同gcd了,那么求[1,r+1]的时候,我们只需要用r+1的这个数即a[r+1],和前一区间内已经求出的gcd进行求gcd就行了,不需要在一个一个枚举每个数求gcd了,也就是相当于要求[k,r+1]的gcd的时候[k,r]的gcd已经在上次求好了,我们直接再与a[r+1]这个数求一次gcd就好了,这样就大大减小了复杂度
由于刚才说的gcd的非递增性质,如果存在一个gcd = g,那么要想得到这g,一定存在一个最大的左端点 lm l m ,即我们从r+1向左遍历的时候,到 lm l m 的时候第一次得到gcd=g,继续向左走的话要么还是g,要么一定变得比g小,因此再往左走就再也不会得到g了,这就是所说的每个gcd都有一个最大的左端点。
所以我们可以利用这最大左端点作为位置,往树状数组里面存,进行不断更新
为了提高效率需要使用离线查询,先把查询存下来,按照右端点从小到大排序
我们仍然是先移动R,在移动过程中不断更新树状数组,这样当R满足第一个查询区间的r,用树状数组查询区间[l,r]即可,因为当我们更新完成后,查询区间[l,r]实际上个相当于查询gcd最大左端点是>=l的个数,小于l的说明得再往左移动才能得到一个新的不同gcd,而右端点我们只更新到了R,也即是要求区间右端点r因此保证了正确性
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pii pair<int,int>
#define MP make_pair
const int N = 1e6+10,M = 1e2+11,mod = 1e9+7,inf = 2e9;
int n,q,a[N],ans[N];
vector<pii> G[N];//G[i]存右端点到i时,往左扩展,的gcd是什么及其得到这个gcd的最大左端点位置(即向左第一次出现这个gcd的位置)
struct Query{
int l,r,id;
bool operator < (const Query &a)const{
return r < a.r;
}
}Q[N];
int C[N],vis[N];
void update(int x,int c){
for(int i = x; i < N; i += i & (-i))
C[i] += c;
}
int getsum(int x){
int s = 0;
for(int i = x; i; i -= i & (-i))
s += C[i];
return s;
}
int gcd(int a,int b){
return b == 0 ? a : gcd(b,a%b);
}
int main(){
while(scanf("%d%d",&n,&q) != EOF){
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
for(int i = 1; i <= n; i++) G[i].clear();
for(int i = 1; i <= n; i++){//这里初始化的就是每个得到的gcd的最大左端点位置,需要仔细理解
int x = a[i];
int y = i;//一开始会把(a[i],i)放进去
for(int j = 0; j < G[i-1].size(); j++){
int res = gcd(x,G[i-1][j].first);
if(x != res){//如果不相等才进入,因为如果相等,更新后还是这个值就会重复放入
G[i].push_back(MP(x,y));
x = res;
y = G[i-1][j].second;//然后更新xy,x为上面求出的gcd,位置就是上一层的gcd的左端点最大位置
//因为这个新的gcd就是和上一层那个gcd求出来的,实际上也即是在上一层gcd的位置求出的
}
}
G[i].push_back(MP(x,y));//把最后一次得到的gcd放入
}
memset(C,0,sizeof(C));
memset(vis,0,sizeof(vis));
for(int i = 1; i <= q; i++){
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id = i;
}
sort(Q+1,Q+q+1);
for(int R = 0, i = 1; i <= q; i++){
while(R < Q[i].r){
R++;//不断右移使得右边界满足条件
for(int j = 0; j < G[R].size(); j++){//不断更新维护树状数组
int res = G[R][j].first;
int ids = G[R][j].second;
if(vis[res]) update(vis[res],-1);//如果之前有过这个gcd了,删掉原来的加入新的
vis[res] = ids;//如果没有直接更新,加入树状数组
update(vis[res],1);
}
}
ans[Q[i].id] = getsum(R) - getsum(Q[i].l-1);//得到区间[Q[i].l,Q[i].r]内的不同gcd的个数
}
for(int i = 1; i <= q; i++){
printf("%d\n",ans[i]);
}
}
return 0;
}