题目链接:https://www.acwing.com/problem/content/247/
操作有两种:求区间最大公约数+区间修改。
两者的结合可以运用更相损减术,维护一个差分数列,用一个树状数组维护数列的差分前缀和,其中要注意的是,树状数组和线段树的维护中要防止索引的越界!!其次,gcd得到的数可能是负数,所以在进入最小性的区间中之后需要对gcd取绝对值,gcd(a,-b)=gcd(a,b)。不能直接对差分之后的区间的gcd取绝对值,因为gcd(a+1,b)!=gcd(-a+1,b)。要在答案返回的时候进行更新。
代码:
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; const int maxn = 500010; ll a[maxn],b[maxn],c[maxn]; int n,m; ll gcd(ll x,ll y){ return y?gcd(y,x%y):x; } struct node{ int l,r; ll gcd; }t[maxn<<2]; ll sum(int x){//差分数列的前缀和 ll ans=0; while(x){ ans+=c[x]; x-=x&(-x); } return ans; } ll add(int x,ll C){//树状数组单点更新 while(x<=n){ c[x]+=C; x+=x&(-x); } } void build(int rt,int l,int r){ t[rt].l=l; t[rt].r=r; if(l==r){ t[rt].gcd=b[l]; return ; } int mid=(t[rt].l+t[rt].r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); t[rt].gcd=gcd(t[rt<<1].gcd,t[rt<<1|1].gcd); } void update(int rt,int pos,ll C){ if(t[rt].l==t[rt].r){ t[rt].gcd+=C; return ; } int mid=(t[rt].l+t[rt].r)>>1; if(pos<=mid)update(rt<<1,pos,C); else update(rt<<1|1,pos,C); t[rt].gcd=gcd(t[rt<<1].gcd,t[rt<<1|1].gcd); } ll query(int rt,int L,int R){ if(t[rt].l>=L && t[rt].r<=R){ return abs(t[rt].gcd); } int mid=(t[rt].l+t[rt].r)>>1; ll ans=0; if(L<=mid)ans=gcd(ans,query(rt<<1,L,R)); if(R>mid)ans=gcd(ans,query(rt<<1|1,L,R)); return ans; } int main(){ cin>>n>>m; b[0]=0; for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); b[i]=a[i]-a[i-1]; } build(1,1,n); char s[10]; int l,r; ll d; while(m--){ scanf("%s",s); if(s[0]=='C'){ scanf("%d%d%lld",&l,&r,&d); update(1,l,d);//维护两个差分数列 add(l,d); if(r+1<=n){//注意防止树状数组的下标越界 update(1,r+1,-d); add(r+1,-d); } }else{ scanf("%d%d",&l,&r); ll ans=gcd(a[l]+sum(l),query(1,l+1,r)); printf("%lld ",ans); } } }