区间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\n",(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; }