hdu5726 GCD ST表+离线

27 篇文章 0 订阅
给定一个序列,每次询问一个区间 
输出这个区间上所有数的GCD,以及GCD与其相同的区间个数(整个序列)

一个连续区间的GCD,用倍增法预处理一下,就能做到 O(1)查询 
对于相同区间计数,就把询问先离线一下 
枚举区间左端点,区间GCD是随右端点递减的,并且是阶梯式的 
并且由于GCD递减的很快,这样一个阶梯只有几层,可以当作log的 
所以对于每一个GCD,二分右端点,就能求出答案
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
using namespace std;
#define sint long long
#define maxn 110000
int gcd(int a,int b)
{
    if(a>b) swap(a,b);
    if(a==0) return b;
    return gcd(b%a,a);
}
map<int,sint>mp;
int n,m,gd[maxn][20];
int query(int l,int r)
{
    int k=(int)log2((double)(r-l+1));
    return gcd(gd[l][k],gd[r-(1<<k)+1][k]);
}
void update(int pos)
{
    int nowgcd=gd[pos][0];
    int togcd=query(pos,n);
    int margin=pos,ll,rr,mid,ans;
    while(nowgcd!=togcd)
    {
        ll=margin+1;
        rr=n;
        ans=-1;
        while(ll<=rr)
        {
            mid=(ll+rr)>>1;
            if(query(pos,mid)!=nowgcd)
            {
                ans=mid;
                rr=mid-1;
            }
            else ll=mid+1;
        }
        mp[nowgcd]+=ans-margin;
        margin=ans;
        nowgcd=query(pos,margin);
    }
    if(margin<=n)
    {
        mp[togcd]+=(n-margin)+1;
    }
}
void solve()
{
    mp.clear();
    for(int i=1;i<=18;i++)
    {
        for(int j=1;j<=n&&j+(1<<i)-1<maxn;j++)
        {
            gd[j][i]=gcd(gd[j][i-1],gd[j+(1<<(i-1))][i-1]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        update(i);
    }
    scanf("%d",&m);
    int l,r;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        int g=query(l,r);
        printf("%d %lld\n",g,mp[g]);
    }
}
int main()
{
    int cas;
    scanf("%d",&cas);
    for(int i=1;i<=cas;i++)
    {
        printf("Case #%d:\n",i);
        memset(gd,0,sizeof(gd));
        scanf("%d",&n);
        for(int j=1;j<=n;j++) scanf("%d",&gd[j][0]);
        solve();
    }
    return 0;
}
/*
1
5
1 2 4 6 7
4
1 5
2 4
3 4
4 4

Case #1:
1 8
2 4
2 4
6 1
*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值