HDU 5726--GCD【倍增】【单调栈】【STL-map】

Description

Give you a sequence of N (N≤100,000)integers:a1,…,an( 0<ai1000,000,000 ). There are Q( Q100,000 ) queries. For each query l,r you have to calculate gcd(al,,al+1,…,ar) and count the number of pairs(l′,r′)( 1l<rN )such that gcd(al′,al′+1,…,ar′) equal gcd(al,al+1,…,ar).

题目大意就是给一个数列,每次给一个l,r,求出[l,r]的gcd和与[l,r]的gcd相同的区间个数。

题解

第一问很简单吧,直接用倍增搞就好了(类似于ST表的搞法)。

看第二问。首先,这里总共会有多少个不同的gcd的值呢,答案是 log2a[i] 个(这非常显然吧,如果gcd要变,一定是变小,最少要除以2)。

这就告诉我们,因为gcd的总数很小,所以可以事先预处理处所有可能出现的gcd的值的答案。问题来了,如何预处理?在预处理时要充分利用gcd的总量很小这一特殊的性质。维护一个栈,表示以某一位为右端点的所有区间中每一种gcd出现的次数,考虑在右边加了一个数,以新数为右端点的序列中的gcd的值就是原来的gcd分别与新数取gcd,别忘了,新加入的数也是gcd的一种情况。至于每种gcd的个数都是很好维护的东西了。

最后,累计答案的时候要用到STL的map(每次刷的时候把以当前为右端点的所有可能情况全部累计到map里)。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#define maxn 100006
#define LL long long
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
map<int,LL>res;
struct data{
    int x;
    LL num;
    bool operator <(const data&b)const{return x<b.x;}
}stack[maxn],stack1[maxn];
int tet,n,m,top,f[maxn][19];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
void make_f(){
    for(int j=1;j<=log2(n);j++)
     for(int i=1;i<=n-(1<<j)+1;i++)
      f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int get(int l,int r){
    int j=log2(r-l+1);
    return gcd(f[l][j],f[r-(1<<j)+1][j]);
}
int main(){
    freopen("gcd.in","r",stdin);
    freopen("gcd.out","w",stdout);
    tet=_read();
    for(int t=1;t<=tet;t++){
        printf("Case #%d:\n",t);
        n=_read();res.clear();
        for(int i=1;i<=n;i++)f[i][0]=_read();
        make_f();
        m=_read();top=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=top;j++)stack1[j].x=gcd(stack[j].x,f[i][0]),stack1[j].num=stack[j].num;
            stack1[++top].x=f[i][0];stack1[top].num=1;
            sort(stack1+1,stack1+1+top);
            int j=1,top1=top;top=0;
            while(j<=top1){
                int k=j;LL sum=0;
                while(k<=top1&&stack1[k].x==stack1[j].x)sum+=stack1[k++].num;
                stack[++top].x=stack1[j].x;stack[top].num=sum;res[stack[top].x]+=stack[top].num;
                j=k;
            }
        }
        while(m--){
            int l=_read(),r=_read(),k=get(l,r);
            printf("%d %lld\n",k,res[k]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值