HDU 5726 GCD 【st表+二分+gcd+离线】

题目链接

题意:给n个数和q次查询,每次查询输入 [l,r] [ l , r ] ,输出在序列中有多少个子区间的gcd值与查询的这个区间相同。

大致可以将题目的主要矛盾分为2点:
1. 快速的查询区间gcd值
2. 由于不能遍历所有子区间,如何尽可能的减少遍历的子区间

解决方法:

1.因为只需要查询,可以简单的用st表实现。

for (int j = 1; j <= 20; j++) {
    for (int i = 1; i + (1 << j) - 1 <= n; i++) {
        st[j][i] = __gcd(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
    }
}
int query(int l, int r) {
    int d = Log[r - l + 1];
    return __gcd(st[d][l], st[d][r - (1 << d) + 1]);
}

2.考虑到gcd具有单调性,即固定左端点,随着右端点慢慢往右,gcd的值只会越来越小。可以通过二分找出就目前的gcd来说,右端点最远的位置。

通过这种方法可以找出所有的gcd,但是由于空间的原因,不能把所有的gcd都存下来。我们只需要记录需要查询的gcd出现的次数,用map来维护,不需要查询的就不管它。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;

int T, n, q, st[25][maxn], Log[maxn], l[maxn], r[maxn], kase = 0;

int query(int l, int r) {
    int d = Log[r - l + 1];
    return __gcd(st[d][l], st[d][r - (1 << d) + 1]);
}

map <int, ll> mp;

int main() {
#ifdef __APPLE__
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
#endif
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    Log[2] = 1;
    for (int i = 3; i <= 100001; i++)   Log[i] = Log[i >> 1] + 1;
    cin >> T;
    while (T--) {
        mp.clear();
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> st[0][i];
        }
        for (int j = 1; j <= 20; j++) {
            for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                st[j][i] = __gcd(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
            }
        }
        cin >> q;
        for (int i = 1; i <= q; i++) {
            cin >> l[i] >> r[i];
            mp[query(l[i], r[i])] = 0;
        }
        for (int i = 1; i <= n; i++) {
            int j = i;
            while (1) {
                int gcd = query(i, j);
                int left = j, right = n;
                int x = 0;
                while (left <= right) {
                    int mid = (left + right) >> 1;
                    if (query(i, mid) == gcd) {
                        x = mid;
                        left = mid + 1;
                    }
                    else {
                        right = mid - 1;
                    }
                }
                if (mp.find(gcd) != mp.end()) {
                    mp[gcd] += x - j + 1;
                }
                j = x + 1;
                if (j > n)
                    break;
            }
        }
        cout << "Case #" << ++kase << ":" << endl;
        for (int i = 1; i <= q; i++) {
            int gcd = query(l[i], r[i]);
            cout << gcd << " " << mp[gcd] << endl;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值