HDU 5381 The sum of gcd

题意: 有一列数,Q个询问区间,求所有子区间的GCD值之和。

思路:线段树先处理区间左右端点都在同一线段树结点内的情况,再加上各在左右子树的情况,即得到当前结点的所有子区间GCD之和。

            需要维护[L,R]区间的所有[L,X]区间和[Y,R]的GCD值,可以发现这些不同的GCD不超过log(ai)个,并且具有递减性质。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int t,n,q,ql,qr;
int st[35],g[35],val[10010],top;
int gcd(int x,int y){
  return y==0 ? x:gcd(y,x%y);
}

void insert(int cnt,int gg){
  int d=gcd(g[top],gg);
  if (d==g[top]) st[top]+=cnt;        //区间GCD具有递减性,g是降序数组,st记录对应个数
  else top++,g[top]=d,st[top]=cnt;
}
struct node {
  int lst[35],lg[35],rst[35],rg[35],ltop,rtop;
  long long sum;
  void update(node a){
    int i,j;
    sum+=a.sum; //l,r都在左区间+都在有区间
    for (i=1;i<=rtop;i++){
        for (j=1;j<=a.ltop;j++){
            sum+=(long long)gcd(rg[i],a.lg[j])*rst[i]*a.lst[j];   //l.r分别落在两个子区间的情况
        }
    }

    for (i=1;i<=ltop;i++) st[i]=lst[i],g[i]=lg[i];        //更新L为区间左端点的GCD情况
    top=ltop;
    for (j=1;j<=a.ltop;j++) insert(a.lst[j],a.lg[j]);
    for (i=1;i<=top;i++) lst[i]=st[i],lg[i]=g[i];
    ltop=top;

    for (i=1;i<=a.rtop;i++) st[i]=a.rst[i],g[i]=a.rg[i];   //更新R为区间右端点的GCD情况
    top=a.rtop;
    for (j=1;j<=rtop;j++) insert(rst[j],rg[j]);
    for (i=1;i<=top;i++) rst[i]=st[i],rg[i]=g[i];
    rtop=top;

  }
} tree[10010*4],ans;

void build(int o,int l,int r){
  if (l==r) {
     tree[o].ltop=tree[o].rtop=1;
     tree[o].lst[1]=tree[o].rst[1]=1;
     tree[o].lg[1]=tree[o].rg[1]=val[l];
     tree[o].sum=val[l];
  } else {
    int mid=(l+r)/2;
    build(o*2,l,mid);
    build(o*2+1,mid+1,r);
    tree[o]=tree[o*2];
    tree[o].update(tree[2*o+1]);
  }
}

void query(int o,int l,int r){
  if (ql<=l && r<=qr){
    if (l==ql) ans=tree[o];
    else ans.update(tree[o]);
  } else {
    int mid=(l+r)/2;
    if (ql<=mid) query(o*2,l,mid);
    if (qr>mid) query(o*2+1,mid+1,r);
  }
}
int main()
{
    scanf("%d",&t);
    int i;
    while (t--){
        scanf("%d",&n);
        for (i=1;i<=n;i++) scanf("%d",&val[i]);
        build(1,1,n);
        scanf("%d",&q);
        while (q--){
            scanf("%d %d",&ql,&qr);
            ans.sum=0;
            query(1,1,n);
            printf("%I64d\n",ans.sum);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值