HDU 5726 GCD(ST&RMQ)

题目链接 GCD

先ST倍增预处理,f[i][j]表示从i开始(包含第i个数)的连续2^j个数的最大公约数。

这样就可以在O(1)内询问得到a[l]到a[r]之间的所有数的最大公约数的值。

然后对于每个数a[i],以这个数为开头的所有子序列的最大公约数的不同值不会超过30个。

而且不同的值是满足单调递减的。

那么就可以二分查找然后把对应值的个数塞进map。

时间复杂度O(Nlog(N))

#include <bits/stdc++.h>

using namespace std;

#define rep(i,a,b)        for(int i(a); i <= (b); ++i)
#define LL            long long

const int N    =    100000        +    10;
const int A    =    30        +    1;

map <int, LL> mp;
int a[N];
int T;
int n, m;
int l, r;
int f[N][A];
int gcd(int a, int b){return b? gcd(b, a % b) : a;}

inline int Solve(int l, int r){
    int k = (int)log2((double)(r - l + 1));
    return gcd(f[l][k], f[r - (1 << k) + 1][k]);    
}

void Work(){
    rep(i, 1, n) f[i][0] = a[i];
    rep(j, 1, 17) rep(i, 1, n) if ((i + (1 << j) - 1) <= n) f[i][j] =gcd(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

void setTable(){
    mp.clear();
    rep(i, 1, n){
        int g = f[i][0], j = i;
        while (j <= n){
            int l = j, r = n;
            while (l < r){
                int mid = (l + r + 1) >> 1;
                if (Solve(l, mid) == g) l = mid; else r = mid - 1;
            }
            mp[g] += l - j + 1;
            j = l + 1;
            g = Solve(i, j);
        }
    }
}
int main(){

    scanf("%d", &T);
    rep(Case, 1, T){
        printf("Case #%d:
", Case);
        scanf("%d", &n);
        rep(i, 1, n) scanf("%d", a + i);
        Work();
        setTable();
        scanf("%d", &m);
        while (m--){
            scanf("%d%d", &l, &r);
            int g = Solve(l, r);
            printf("%d %lld
", g, mp[g]);
        }
    }

    return 0;
    
}
原文地址:https://www.cnblogs.com/cxhscst2/p/6720256.html