【gcd分块+离线+树状数组】HDU5869[Different GCD Subarray Query]题解

题目概述

给出序列 {An}m 个询问 (L,R) ,定义 GCD(i,j)=gcd(ai,ai+1,,aj) ,每个询问求 [L,R] 范围内所有子序列 GCD(i,j) 不相同的个数。

解题报告

gcd和and,or一样有分块性质,由于gcd每次至少 ÷2 ,所以去重之后只有 log2n 块。

倒过来处理,假设目前处理到 i ,我们会发现 i 的某一个块只有左端点具有贡献,同时,数值相等但 i 不同的块只有最左边的左端点才有贡献,所以,只要记录 lst[i] 表示数值为 i 的块最左边的左端点,然后用树状数组就可以知道 (i,R) 的答案了。但是询问并不一定按照降序给出,由于不强制在线,所以排个序就行了。

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fr first
#define sc second
#define mp make_pair
using namespace std;
typedef long long LL;
const int maxn=1e5,maxm=1e5,maxa=1e6,Log=17;

int n,m,a[maxn+5],ID[maxn+5],blk,lst[maxa+5];LL ans[maxm+5];
pair<int,int> q[maxm+5],b[Log+5];

int gcd(int a,int b) {if (!b) return a;return gcd(b,a%b);}
LL c[maxn+5];
inline void Update(int x,int tem) {for (int p=x;p<=n;p+=p&-p) c[p]+=tem;}
inline LL Sum(int x) {LL sum=0;for (int p=x;p;p-=p&-p) sum+=c[p];return sum;}
inline void Insert(int p)
{
    b[++blk]=mp(a[p],p);
    for (int i=1;i<=blk;i++) b[i].fr=gcd(b[i].fr,a[p]);
    int now=blk;blk=1;
    for (int i=2;i<=now;i++) if (b[i].fr==b[blk].fr) b[blk].sc=b[i].sc; else
    b[++blk]=b[i];
    for (int i=1;i<=blk;i++) if (b[i].sc<lst[b[i].fr])
    {
        if (lst[b[i].fr]<=n) Update(lst[b[i].fr],-1);
        Update(lst[b[i].fr]=b[i].sc,1);
    }
}
inline bool cmp(int a,int b) {return q[a]>q[b];}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    while (~scanf("%d%d",&n,&m))
    {
        memset(c,0,sizeof(c));blk=0;for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        for (int i=1;i<=m;i++) ID[i]=i,scanf("%d%d",&q[i].fr,&q[i].sc);
        sort(ID+1,ID+1+m,cmp);memset(lst,63,sizeof(lst));
        for (int i=1,now=n;i<=m;i++)
        {
            while (q[ID[i]].fr<=now) Insert(now--);
            ans[ID[i]]=Sum(q[ID[i]].sc)-Sum(q[ID[i]].fr-1);
        }
        for (int i=1;i<=m;i++) printf("%lld\n",ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值