HDU 5726-GCD

题意

给出n(n<=1e5)个数(a[i]<=1e9),q(q <= 1e5)个查询,对于每个查询(l,r),输出a[l]到a[r]之间的数的gcd ansa,以及原数组中gcd等于ansa的区间个数。

思路

对于第一种查询,可以使用st表在O(1)的时间内给出答案。对于第二种查询,考虑以i为左端点的区间,其gcd一定随着j的增大而递减。更准确的描述是呈阶梯状下降的。因此可以采用二分的思想,找到每个水平段的长度,并将此段的gcd值对应的区间数量加上此水平段的长度(可以使用map存储每个gcd值对应的答案)。得到数组后做预处理,从1~n枚举左端点i,算法总体时间复杂度(nlognlogn)。

代码

#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 2e5;
const int maxlog = 20;
int n;
int s[maxn];
int dp[maxn][maxlog];
int gcd(int a,int b)
{
    if(b == 0) return a;
    return gcd(b,a%b);
}
map<int,ll> mp;
int rmq(int l,int r)
{
    int k = 0;
    while((1<<(k+1)) <= r - l+1) k++;
    return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
    int t;
    scanf("%d",&t);
    int ks = 1;
    while(t--)
    {
        mp.clear();
        scanf("%d",&n);
        for(int i = 0; i < n ;i++)
            scanf("%d",s+i);
        for(int i = 0; i < n; i++)
            dp[i][0] = s[i];
        for(int j = 1 ; j < maxlog; j++)
            for(int i = 0;i < n; i++)
            {
                dp[i][j] = dp[i][j-1];
                if(i+(1<<(j-1)) < n)
                dp[i][j] = gcd(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
            }
        for(int i = 0; i < n; i++)
        {
            int l = i-1,r = n;
            while(l+1 < n)
            {
                int st = rmq(i,l+1);
                int pre = l;
                while(l+1 < r)
                {
                    int mid = (l+r)/2;
                    int gg = rmq(i,mid);
                    if(gg >= st)
                        l = mid;
                    else
                        r = mid;
                }
                ll cnt = l - pre;
                if(mp.find(st) == mp.end())
                    mp[st] = 0;
                mp[st] += cnt;
                r = n;
            }
        }
        printf("Case #%d:\n",ks);
        ks++;
        int q;
        scanf("%d",&q);
        for(int i = 0; i <q; i++)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            l--,r--;
            int sg = rmq(l,r);
            printf("%d %lld\n",sg,mp[sg]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值