hdu5726 GCD ST表+離線

給定一個序列,每次詢問一個區間 
輸出這個區間上所有數的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
*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章