Interval GCD(线段树带修改求区间gcd)

D. Interval_GCD

蓝书题目 Interval GCD
题意:给定n个数字Ai,M次操作(N:5e5,M:5e5)操作有两种:

  • C l r d 将A[l,r]加上d
  • Q l r 询问A[l,r]的最大公约数

思路:
gcd ⁡ ( a 1 , a 2 , a 3 , a 4 . . . , a n ) = gcd ⁡ ( a 1 , a 2 − a 1 , a 3 − a 2 , a 4 − a 3 . . . a n − a n − 1 ) \gcd(a_1,a_2,a_3,a_4...,a_n)=\gcd(a_1,a_2-a_1,a_3-a_2,a_4-a_3...a_n-a_{n-1}) gcd(a1,a2,a3,a4...,an)=gcd(a1,a2a1,a3a2,a4a3...anan1)
所以: gcd ⁡ ( a l , a l + 1 , a l + 2 . . . a r ) = g c d ( a l , a l + 1 − a l , a l + 2 − a l + 1 . . . a r − a r − 1 ) \gcd(a_l,a_{l+1},a_{l+2}...a_{r})=gcd(a_l,a_{l+1}-a_{l},a_{l+2}-a_{l+1}...a_{r}-a_{r-1}) gcd(al,al+1,al+2...ar)=gcd(al,al+1al,al+2al+1...arar1)
这样维护的数列就从原数列A转换成了A的差分数列B,现在剩下的就是对于 a l a_l al的维护
好处是:
对于每次操作C(区间加)而言,只需要单点修改B数列的 l l l r + 1 r+1 r+1 位置( l l l 处加d; r + 1 r+1 r+1处减d)

做法:
建立线段树维护差分数列B(为了计算差分数列B的 gcd ⁡ \gcd gcd
建立梳妆数字维护数列A(为了计算 a l a_l al

struct SegmentTree{
    
    
    int l,r;
    LL dat;//计算gcd
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define dat(x) tree[x].dat
}tree[maxn*4];
LL a[maxn],b[maxn],c[maxn];
int n,m;

void build(int p,int l,int r){
    
    
    l(p)=l,r(p)=r;
    if(l==r){
    
    dat(p)=b[l];return;}
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    dat(p)=lgcd(dat(p*2),dat(p*2+1));
}

/*void lazy(int p){
    if(add(p)){
        sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1);
        sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1);
        add(p*2)+=add(p);
        add(p*2+1)+=add(p);
        add(p)=0;
    }
}*/

void change(int p,int x,LL z){
    
    
    if(l(p)==r(p)){
    
    dat(p)+=z;return;}
    int mid=(l(p)+r(p))/2;
    if(x<=mid) change(p*2,x,z);
    else change(p*2+1,x,z);
    dat(p)=lgcd(dat(p*2),dat(p*2+1));
}

LL ask(int p,int l,int r){
    
    
    if(l<=l(p)&&r>=r(p)) return abs(dat(p));
    int mid=(l(p)+r(p))/2;
    LL ans=0ll;
    if(l<=mid)ans=lgcd(ans,ask(p*2,l,r));
    if(r>mid)ans=lgcd(ans,ask(2*p+1,l,r));
    return abs(ans);
}

void updata(int i,LL k){
    
    //在i位置上加上k
    while(i<=n){
    
    
        c[i]+=k;
        i+=i&(-i);
    }
}///区间[p,q]加c:updata(p,c);updata(q+1,-c);

LL getsum(int i){
    
    //求i的前缀和(1~i)
    LL res=0;
    while(i){
    
    
        res+=c[i];
        i-=i&(-i);
    }
    return res;
}///区间[p,q]:getsum(q)-getsum(p-1)

int main(){
    
    
    cin>>n>>m;
    memset(c,0,sizeof c);
    for(int i=1;i<=n;i++){
    
    cin>>a[i];b[i]=a[i]-a[i-1];}
    build(1,1,n);
    int x,y;
    LL z;
    char op;
    while(m--){
    
    
        cin>>op>>x>>y;
        if(op=='C'){
    
    
            cin>>z;
            updata(x,z);//维护数组A
            change(1,x,z);//维护数组B
            if(y+1<=n){
    
    change(1,y+1,-1ll*z);updata(y+1,-1l*z);}
        }else{
    
    cout<<lgcd(a[x]+getsum(x),ask(1,x+1,y))<<endl;}
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44986601/article/details/105759251