HDU 5726 GCD(ST+二分)

81 篇文章 0 订阅
15 篇文章 0 订阅

Description
给出一个长度为n的序列a[1],…,a[n],q次查询,对于每次查询(l,r),输出gcd(a[l],…,a[r]),并且输出满足条件的区间[ll,rr]的个数,使得gcd(a[ll],…,a[rr])=gcd(a[l],…,a[r])
Input
第一行为一整数T表示用例组数,每组用例首先属于序列长度n,之后n个整数a[i]表示该序列,然后输出查询次数q,之后q行每行两个整数li,ri表示一次查询(n,q<=10^5,0< ai<=10^9)
Output
对于每次查询,输出gcd(a[l],…,a[r]),并且输出满足条件的区间[ll,rr]的个数(ll<=rr)
Sample Input
1
5
1 2 4 6 7
4
1 5
2 4
3 4
4 4
Sample Output
Case #1:
1 8
2 4
2 4
6 1
Solution
首先用ST表求出区间gcd,那么第一个问题就可以O(1)查询,对于第二个问题,用gcd(l,r)表示gcd(a[l],…,a[r]),固定左端点l,随着右端点r的递增,gcd(l,r)阶梯型递减,而且gcd(l,r)减的很快,最多有log(10^9)不同的gcd(l,r)值,故对每个gcd,枚举左端点,二分右端点即可得到答案
Code

#include<cstdio>
#include<iostream>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
#define maxn 111111
int T,n,q,a[maxn],res=1;
map<int,ll>m;
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int st[maxn][22];
void ST()
{
    for(int i=0;i<n;i++)st[i][0]=a[i];
    int k=(int)(log(1.0*n)/log(2.0));
    for(int j=1;j<=k;j++)   
        for(int i=0;i+(1<<j)<=n;i++)  
            st[i][j]=gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int query(int l,int r)  
{  
    int k=(int)(log(1.0*(r-l+1))/log(2.0));  
    return gcd(st[r-(1<<k)+1][k],st[l][k]);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        m.clear();
        scanf("%d",&n);
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        ST();
        for(int i=0;i<n;i++)
        {
            int t,ll=i;
            while(ll<n)
            {
                t=query(i,ll);
                int l=ll,r=n-1;
                while(l<r)
                {
                    int mid=(l+r+1)>>1;
                    if(query(i,mid)>=t)l=mid;
                    else r=mid-1;
                }
                m[t]+=l-ll+1;
                ll=l+1;
            }
        }
        scanf("%d",&q);
        printf("Case #%d:\n",res++);
        while(q--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            l--,r--;
            printf("%d %I64d\n",query(l,r),m[query(l,r)]);
        }   
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值