Different GCD Subarray Query HDU - 5869(gcd性质+树状数组+离线查询)

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值