GCD HDU - 5726

点击打开链接

第一问区间gcd好求 难在第二问要求整个序列有多少个区间的gcd与之相等

网上很多用rmq做的 整体复杂度是n*(log(n)^3) 这个做法用线段树就会被卡时间

首先要知道 两个数求gcd 除非两数相等 否则只会越来越小且至少变小2倍 满足一定单调性

 

先说网上的rmq做法

线性枚举确定一个左端点l 再向右找一个右端点r 使这个区间的gcd等于左端点出的数值 即[l,l] [l,l+1] [l,l+2] ... [l,r] 这些小区间的gcd值都一样 而找右端点可以根据上述的单调性来二分 这是一个logn 区间查询gcd又是一个logn 然后再找下一个这样的区间 又是一个logn

 

其实二分还有区间查询的logn*logn可以用线段树一次logn的查询解决

每次查询找和当前gcd值不相等的区间的左端点 即分界点 在这个过程中 如果每个在查询范围内的区间是当前gcd的倍数 那分界点肯定不在这个区间 可以递归返回去找更右边的区间了 反之 分界点可定在这个区间内 然后继续递归找左右子区间

 

可能有人会有疑问 如果固定左端点后 每次移动右端点得到的gcd值都不同 都变成一个新的区间 那不就成o(n)了吗 在上面还说了 两个数求gcd 不相等则至少变小2倍 这样gcd很快就变成 1 了 所以根据这个性质知道 整个序列内最多就有log(n)个区间给你查

#include <bits/stdc++.h>
using namespace std;
#define ll long long

struct node
{
    int l;
    int r;
    int val;
};

map <int,ll> mp;
node tree[400010];
int num[100010];
int n,q;

int getgcd(int a,int b)
{
    int t;
    while(b>0)
    {
        t=b;
        b=a%b;
        a=t;
    }
    return a;
}

void pushup(int cur)
{
    tree[cur].val=getgcd(tree[2*cur].val,tree[2*cur+1].val);
}

void build(int l,int r,int cur)
{
    int m;
    tree[cur].l=l;
    tree[cur].r=r;
    if(l==r)
    {
        tree[cur].val=num[l];
        return;
    }
    m=(l+r)/2;
    build(l,m,2*cur);
    build(m+1,r,2*cur+1);
    pushup(cur);
}

int queryI(int pl,int pr,int gcd,int cur)
{
    int res;
    if(pl<=tree[cur].l&&tree[cur].r<=pr&&tree[cur].val%gcd==0) return 0;
    if(tree[cur].l==tree[cur].r) return tree[cur].l;
    res=0;
    if(pl<=tree[2*cur].r) res=queryI(pl,pr,gcd,2*cur);
    if(res==0&&pr>=tree[2*cur+1].l) res=queryI(pl,pr,gcd,2*cur+1);
    return res;
}

int queryII(int pl,int pr,int cur)
{
    int res;
    if(pl<=tree[cur].l&&tree[cur].r<=pr)
    {
        return tree[cur].val;
    }
    res=0;
    if(pl<=tree[2*cur].r) res=getgcd(queryII(pl,pr,2*cur),res);
    if(pr>=tree[2*cur+1].l) res=getgcd(queryII(pl,pr,2*cur+1),res);
    return res;
}

void init()
{
    int i,p,res,gcd;
    mp.clear();
    for(i=1;i<=n;i++)
    {
        p=i,gcd=num[p];
        while(p<=n)
        {
            res=queryI(p,n,gcd,1);
            if(res==0) res=n+1;
            mp[gcd]+=(res-p);
            gcd=getgcd(gcd,num[res]);
            p=res;
        }
    }
}

int main()
{
    int t,cas,i,l,r,gcd;
    scanf("%d",&t);
    for(cas=1;cas<=t;cas++)
    {
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&num[i]);
        }
        build(1,n,1);
        init();
        scanf("%d",&q);
        printf("Case #%d:\n",cas);
        while(q--)
        {
            scanf("%d%d",&l,&r);
            gcd=queryII(l,r,1);
            printf("%d %lld\n",gcd,mp[gcd]);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值