思路
这题用到了树状数组维护增量数组、线段树维护差分数组的高端操作。不会差分的小朋友左转学习
设原数组第 $i$ 位的值为 $a_i$,$d_i=a_i-a_{i-1}$,则有(这里认为 $a_0=0$):
$a_n=\sum\limits_{i=1}^nd_i$
根据“更相减损术”则有:$gcd(a,b)=gcd(a,b-a)$
动动脑筋推广一下得:$gcd(a,b,c)=((a,b),(b,c))=(a,b-a,c-b)$
那么显而易见地:$gcd(a_1,a_2,…,a_n)=gcd(a_1,a_2-a_1,…,a_n-a_{n-1})$
嘿嘿嘿!这不是差分数组的形式吗?
也就是说,当遇到区间加减操作时,该区间的最大公因数并不会改变——因为两两之间的差没有变,
我们只需要单点修改该区间的两个端点即可——因为他们与相邻的区间外的点的差发生了改变,即 $GCD$ 发生了改变。
求解
要注意求解时不能 $findgcd(1,l,r)$ 草草了事,你这是求的 $gcd(d_l,d_{l+1},…,d_r)$,即 $gcd(a_l-a_{l-1},a_{l+1}-a_l,…,a_r-a_{r-1})$,
然而看看我们的式子 $gcd(a_l,a_{l+1},…a_r)=gcd(a_l,a_{l+1}-a_l,…,a_r-a_{r-1})$ 发现第一项有所不同了吗?
所以我们有 $ans=gcd(now,findgcd(1,l+1,r))$
最后上代码:
#include<cstdio>
#include<cstring>
#define ll long long
#define lc tr[now].son[0]
#define rc tr[now].son[1]
using namespace std;
const int N=500010;
inline ll gcd(ll a,ll b) {return a?gcd(b%a,a):b;}
inline ll myabs(ll a) {return a<0?-a:a;}
template <typename T>
inline void read(T &x)
{
x=0;bool f=false;
char ch=getchar();
while(ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
x=f?-x:x;
}
struct node
{
int l,r,son[2];ll c;
}tr[N<<2];int len,root;
ll a[N],b[N];//原数组、差分数组
void bt(int &now,int l,int r)
{
now=++len;tr[now].l=l;tr[now].r=r;
if(l==r) lc=rc=-1,tr[now].c=b[l];
else
{
int mid=(l+r)>>1;
bt(lc,l,mid);
bt(rc,mid+1,r);
tr[now].c=gcd(tr[lc].c,tr[rc].c);
}
}
void change(int now,int x,ll k)
{
if(tr[now].l==tr[now].r) {tr[now].c+=k;return ;}
int mid=(tr[now].l+tr[now].r)>>1;
if(x<=mid) change(lc,x,k);
else change(rc,x,k);
tr[now].c=gcd(tr[lc].c,tr[rc].c);
}
ll findgcd(int now,int l,int r)
{
if(tr[now].l==l && tr[now].r==r) return tr[now].c;
int mid=(tr[now].l+tr[now].r)>>1;
ll ans;
if(r<=mid) ans=findgcd(lc,l,r);
else if(l>mid) ans=findgcd(rc,l,r);
else ans=gcd(findgcd(lc,l,mid),findgcd(rc,mid+1,r));
return myabs(ans);
}
ll sum[N];//树状数组(维护增量)
int lowbit(int x) {return x&-x;}
void add(int x,ll k) {for(int i=x;i<=N;i+=lowbit(i)) sum[i]+=k;}
ll getsum(int x) {ll ret=0;for(int i=x;i;i-=lowbit(i)) ret+=sum[i];return ret;}
int main()
{
int n,m;read(n);read(m);
for(int i=1;i<=n;i++) read(a[i]),b[i]=a[i]-a[i-1];
len=0;bt(root,1,n);
for(int i=1;i<=m;i++)
{
char qr[2];scanf("%s",qr);
int x,y;ll z;read(x);read(y);
if(qr[0]=='Q')
{
if(x>y) x^=y^=x^=y;
ll now=a[x]+getsum(x);
printf("%lld\n",gcd(now,findgcd(1,x+1,y)));
}
else
{
read(z);
add(x,z);add(y+1,-z);
change(1,x,z);
if(y<n) change(1,y+1,-z);
}
}
return 0;
}
当遇到区间加减操作时,该区间的最大公因数并不会改变——因为两两之间的差没有变,真的假的,为什么我理解不了啊
单点修改了这个节点的最大公约数之后,在递归回溯的过程pushup就把他父节点的最大公约数全部依靠我修改后的数据进行更新了
题解真好
这码风丑的可以,```cpp的效果也不好看