线段树练习
思路:
这是一个区间加一个数,和查询区间最大公约数的问题,这里按理说涉及到区间改了,线段树的话就需要加上懒标记了,但是我们通过分析知道这里只是区间整体加一个数或者整体减一个数,这里我们思考一个问题
区间 x,y,z 区间加上a 后 x+a, y+a, z+a这两个区间的最大公约数有什么联系吗,很明显没有任何联系,对于区间修改我们还学过一个差分数组,可以把区间的修改变为l和r+1的单值修改,我们要查询某一个数值就变为了区间前缀和的询问,我们来看一下:x,y,z 和 x,y-x,z-y 这两个区间的最大公约数之间的联系,我们发现这两个的最大公约数是相等的,证明一下:
先留下这个证明,以后有空再证~~~
所以这个问题就可以转变为不需要懒标记的线段树维护一个差分数组的问题,那么我们来找一下答案的规律比如区间(x y z p)它的差分数组 就是(x, y-x, z-y,p-z)比如我们想求2–4的最大公约数(x位置为1)因为 我们线段树维护的是差分数组,所以我们只有(y-x,z-y,p-z)这时候我们需要y,z-y,p-y,后两个很好得到只要query(1,l+1,r)就行,得到y就需要我们求一下前缀和了需要query(1,1,l)就行了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5+10;
struct node{
int l,r;
ll sum,v;
}tr[N<<2];
ll arr[N];
int n,m;
ll gcd(ll a, ll b)
{
return b? gcd(b,a%b):a;
}
void pushup(node &u, node &a, node &b)
{
u.sum=a.sum+b.sum;
u.v=gcd(a.v,b.v);
}
void pushup(int u)
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r)
{
if(l==r)
{
ll b = arr[l]-arr[l-1];
tr[u]={l,l,b,b};
}
else
{
tr[u].l=l;
tr[u].r=r;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int x,ll v)
{
if(tr[u].l==x&&tr[u].r==x)
{
ll b= tr[u].sum+v;
tr[u]={x,x,b,b};
}
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else if(x>mid) modify(u<<1|1,x,v);
pushup(u);
}
}
node query(int u,int l,int r)
{
if(l>r)
return {0,0,0,0};
if(tr[u].l>=l&&tr[u].r<=r)
{
return tr[u];
}
else
{
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);
else if(l>mid) return query(u<<1|1,l,r);
else
{
auto a = query(u<<1,l,r);
auto b = query(u<<1|1,l,r);
node res;
pushup(res,a,b);
return res;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%lld",&arr[i]);
}
build(1,1,n);
while(m--)
{
char op[2];
int x,y;
scanf("%s%d%d",op,&x,&y);
if(*op=='Q')
{
ll res=abs(gcd(query(1,1,x).sum,query(1,x+1,y).v)) ;
printf("%lld\n",res);
}
else
{
ll k;
scanf("%lld",&k);
modify(1,x,k);
if(y+1<=n)
modify(1,y+1,-k);
}
}
return 0;
}