HDU5726 GCD(RMQ+二分)

HDU5726 GCD(RMQ+二分)

题面

题意:给一个长度为 N N N 的序列 a 1 , . . . , a n a_1, ..., a_n a1,...,an,有 Q Q Q 个询问,每个询问给出一个区间 [ l , r ] [l, r] [l,r],问序列该区间中所有数字的 g c d gcd gcd d d d,以及整个序列中有多少个区间的 g c d gcd gcd 值为 d ​ d​ d

范围 N ≤ 100000   ,   0 < a i ≤ 1000000000   ,   Q ≤ 100000   ,   1 ≤ l < r ≤ N ​ N \le 100000~,~0 < a_i \le 1000000000~,~Q \le 100000~,~1 \le l < r \le N​ N100000 , 0<ai1000000000 , Q100000 , 1l<rN

分析:两个问题需要解决,求区间 [ l , r ] [l, r] [l,r] g c d gcd gcd d d d 以及整个序列中所有 g c d gcd gcd 值为 d d d 的区间对数。

第一个问题很容易解决,也有很多种方式,比如线段树维护等。

第二个问题容易想到整个序列中 g c d gcd gcd 值为 d d d 的数量是固定的,不需要每次询问的时候都重新求解,考虑进行预处理。

如何进行预处理呢?简单的想法是从左到右遍历序列的每个数字当做起点,再加一重循环确定右边界,逐步计算区间的 g c d gcd gcd 值,使用 m a p map map 统计各个 g c d gcd gcd 值的个数。显然这里的复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

考虑进行优化。我们可以发现在确定右边界的时候并不是每加入一个数字就会导致该区间的 g c d ​ gcd​ gcd 值发生改变,且 g c d ​ gcd​ gcd 值必定是随着数字的加入单调不增,那么我们可以使用二分计算出连续相同 g c d ​ gcd​ gcd 值的数量进行统计。如果加入数字后该区间的 g c d ​ gcd​ gcd 值发生改变,至少是减少了一倍,那么对于同一个起点,需要进行二分的次数不会超过 l o g 2 ( n ) ​ log_2(n)​ log2(n) 次,而二分的时候还需要在内部计算区间的 g c d ​ gcd​ gcd 值,假设使用线段树维护,那么计算区间 g c d ​ gcd​ gcd 值时间复杂度为 O ( l o g n ) ​ O(logn)​ O(logn),因此这里的总体时间复杂度为 O ( n ∗ l o g n ∗ l o g n ∗ l o g n ) ​ O(n*logn*logn*logn)​ O(nlognlognlogn),遍历*二分次数*二分*求区间 g c d ​ gcd​ gcd,依然会 TLE。

继续进行优化,把线段树换成 R M Q RMQ RMQ O ( n l o g n ) O(nlogn) O(nlogn) 进行预处理, O ( 1 ) O(1) O(1) 进行区间查询,那么总体时间复杂度就优化到了 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),这样就可以通过本题了。

详见代码。

Code

#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;

const int MAXN = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

int arr[MAXN];

// RMQ数组,2^20 > 1e5
int gcd[MAXN][20];

// RMQ预处理
void RMQ()
{
    // 每个点的gcd值就是本身
    for (int i = 0; i < n; i++)
    {
        gcd[i][0] = arr[i];
    }
    // 2^18 > 1e5,注意先处理j比较小的,再处理大的
    for (int j = 1; j < 18; j++)
    {
        for (int i = 0; i < n; i++)
        {
            // 没有越界
            if (i + (1 << j) - 1 < n)
            {
                gcd[i][j] = __gcd(gcd[i][j - 1], gcd[i + (1 << j - 1)][j - 1]);
            }
        }
    }
}

// 查询区间gcd
int ask_interval(int l, int r)
{
    int k = (int)log2((double)(r - l + 1));
    return __gcd(gcd[l][k], gcd[r - (1 << k) + 1][k]);
}

// mp统计各种gcd值的个数
map<int, int> mp;

// 预处理出每种gcd值的个数
void getGcdNum()
{
    for (int i = 0; i < n; i++)
    {
        // d:当前区间gcd值,j:当前区间左边界
        int d = gcd[i][0], j = i;
        while (j < n)
        {
            // 二分确定相同gcd值的区间
            int l = j, r = n - 1, ans = j;
            while (l <= r)
            {
                int mid = l + r >> 1;
                if (ask_interval(i, mid) == d)
                {
                    ans = mid;
                    l = mid + 1;
                }
                else
                {
                    r = mid - 1;
                }
            }
            // 此时[j,j],[j,j+1],...,[j,j+ans]的区间gcd值相同,统计答案
            mp[d] += ans - j + 1;
            // 处理下一个gcd值
            j = ans + 1;
            d = ask_interval(i, j);
        }
    }
}

signed main()
{
    int T = read();
    int kase = 1;
    while (T--)
    {
        // 注意清空
        mp.clear();
        n = read();
        for (int i = 0; i < n; i++)
        {
            arr[i] = read();
        }
        // 预处理
        RMQ();
        getGcdNum();
        cout << "Case #" << kase++ << ":" << endl;
        int Q = read();
        while (Q--)
        {
            int l, r;
            l = read(), r = read();
            // 区间查询
            int d = ask_interval(l - 1, r - 1);
            // 直接输出答案
            cout << d << " " << mp[d] << endl;
        }
    }
    return 0;
}

【END】感谢观看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值