区间gcd (带修) 线段树

题目链接:https://ac.nowcoder.com/acm/contest/1033/B

再次吐槽CH

区间gcd再加区间修改。

一般求gcd的时候辗转相除法。

gcd(x,y)=gcd(x,y-x)

那么可以把这个公式推到3个项。

gcd(x,y,z)=gcd(x,y-x,z-y)

可以看出来这是一个差分数列。

原数列为a[],差分数列为b[]。

这样就可以用线段树在b[]上单点修改,l加上d,r+1减去d。

再用树状数组维护出一个c[]数组,区间修改,单点查询a[]数组。

这样的话答案就是

gcd(a[l]+aks_c(l),ask_t(1,1,n,l+1,r))

就可以较小复杂度处理了。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500001;
int n,m;
ll c[maxn],a[maxn],b[maxn];
struct node{
    ll ans;
    #define ans(x) t[x].ans
}t[maxn<<2];
inline ll gcd(ll x,ll y){
    return y ? gcd(y,x%y) : x;
}
inline void build(int p,int l,int r){
    if(l==r){
        ans(p)=b[l];return;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);build(p<<1|1,mid+1,r);
    ans(p)=gcd(ans(p<<1),ans(p<<1|1));
}
inline void change(int p,int l,int r,int x,ll d){
    if(l==r&&r==x){
        ans(p)+=d;return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) change(p<<1,l,mid,x,d);
    else change(p<<1|1,mid+1,r,x,d);
    ans(p)=gcd(ans(p<<1),ans(p<<1|1));
}
inline ll askt(int p,int l,int r,int x,int y){
    if(x<=l&&r<=y)
        return abs(ans(p));
    int mid=(l+r)>>1;ll res=0;
    if(x<=mid) res=gcd(res,askt(p<<1,l,mid,x,y));
    if(y>mid) res=gcd(res,askt(p<<1|1,mid+1,r,x,y));
    return abs(res);
}
inline void add(int x,ll y){
    for(;x<=n;x+=(x&-x))
        c[x]+=y;
}
inline ll askc(int x){
    ll ans=0;
    for(;x;x-=(x&-x))
        ans+=c[x];
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        b[i]=a[i]-a[i-1];
    }
    build(1,1,n);
    for(int i=1;i<=m;i++){
        char ch[2];scanf("%s",ch);
        int l,r;scanf("%d%d",&l,&r);
        if(ch[0]=='Q'){
            printf("%lld
",(ll)gcd(a[l]+askc(l),askt(1,1,n,l+1,r)));
        }else{
            ll d;scanf("%lld",&d);
            change(1,1,n,l,d);
            if(r<n) change(1,1,n,r+1,-d);
            add(l,d);add(r+1,-d);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/ChrisKKK/p/11489700.html