区间最大公约数【线段树+树状数组】

区间最大公约数【线段树+树状数组】

CH4302ACwing246

题目:

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
  2. Q l r,表示询问 A[l],A[l+1],…,A[r]的最大公约数(GCD)。

对于每个询问,输出一个整数表示答案。

输入:

第一行两个整数 N,M。

第二行 N 个整数 A[i]。

接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。

输出:

对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围:

N≤500000,M≤100000,
1≤A[i]≤1018,
|d|≤1018

样例输入:
5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 1 5
C 3 3 6
Q 2 4
样例输出:
1
2
4

思路:

性质:
1.gcd(a1,a2) = gcd(a1,a2-a1),归纳:
2.gcd(a1,a2,a3,...) = gcd(a1,a2-a1,a3-a2...)
因为涉及到了区间修改,我们可以利用差分来做这个题,然后每个点的值使用树状数组来维护前缀和,
而区间[l,r]修改的话,就在差分数组d[l]加上一个数,同时在d[r+1]减去一个数。
同时用一个线段树来维护区间最大公约数,每次修改操作只对d[l]和d[r+1]产生了影响,因此线段树也只需要修改两个点。

代码:

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

#define int long long
const int N = 5e5+5;

int a[N],d[N + 5],tree1[N + 5]; //分别存储初始数组、差分数组、树状数组
struct Node{
    int l,r;        //存储区间
    int m_gcd;      //区间最大gcd
} tree2[N << 2];    //存储线段树
int n,m;

int gcd(int x,int y){   //返回x,y的最大公约数
    return y == 0 ? x : gcd(y,x % y);
}

void build(int p,int l,int r){  //建立结点p,区间为[l,r]
    tree2[p].l = l,tree2[p].r = r;
    if(l == r){
        tree2[p].m_gcd = d[l];  //区间最大公约数就是本身
        return;
    }
    int lc = p<<1,rc = p<<1|1,mid = l+r>>1;
    build(lc,l,mid);            //建立左孩子结点
    build(rc,mid + 1,r);        //建立右孩子结点
    tree2[p].m_gcd = gcd(tree2[lc].m_gcd,tree2[rc].m_gcd);  //该结点的最大区间公约数是左孩子、右孩子的gcd
}

void update2(int p,int x,int k){    //将d[x]加上k
    if(tree2[p].l == x && tree2[p].r == x){
        tree2[p].m_gcd += k;
        return;
    }
    int lc = p<<1,rc = p<<1|1,mid = tree2[p].l+tree2[p].r>>1;
    if(x<=mid)
        update2(lc,x,k);
    else
        update2(rc,x,k);
    tree2[p].m_gcd = gcd(tree2[lc].m_gcd,tree2[rc].m_gcd);
}

int get_gcd(int p,int l,int r){     //查找区间最大公约数
    if(tree2[p].l == l && tree2[p].r == r)
        return tree2[p].m_gcd;
    int lc = p<<1,rc = p<<1|1,mid = tree2[p].l+tree2[p].r>>1;
    if(r <= mid)
        return get_gcd(lc,l,r);
    else if(l > mid)
        return get_gcd(rc,l,r);
    else
        return gcd((get_gcd(lc,l,mid)),(get_gcd(rc,mid + 1,r)));
}

void update1(int x,int y){       //用于更新树状数组d[x] += y
    for(;x <= n;x += x & -x)
        tree1[x] += y;
}
int get_sum(int x){             //用于查找前缀和d[1]+...+d[x]
    int ans = 0;
    for(;x;x -= x & -x)
        ans += tree1[x];
    return ans;
}

signed main(){
    cin>>n>>m;
    for(int i = 1;i <= n;++i){
        cin>>a[i];
        d[i] = a[i] - a[i-1];       //差分
        update1(i,d[i]);            //建立树状数组
    }
    build(1,1,n);               //建立线段树
    char op;int l,r,k;
    while(m--){
        cin>>op;
        if(op == 'C'){
            cin>>l>>r>>k;
            update1(l,k);update1(r+1,-k);   //更新树状数组
            update2(1,l,k);                 //更新线段树
            if(r < n)                           //如果r+1超过了n,那么就不用更新线段树了
                update2(1,r+1,-k);
        }
        else{
            cin>>l>>r;
            if(l == r)
                cout<<abs(get_sum(l))<<"\n";
            else
                cout<<abs(gcd(get_sum(l),get_gcd(1,l + 1,r)))<<"\n";
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alan_Lowe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值