[线段树 差分 区间转单点]区间最大公约数

Posted 鱼竿钓鱼干

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[线段树 差分 区间转单点]区间最大公约数相关的知识,希望对你有一定的参考价值。

[线段树 差分]区间最大公约数

在这里插入图片描述

思路

操作1:区间[L,R]增加一个数
操作2:求区间最大公约数
g c d ( a [ l ] , a [ l + 1 ] , a [ l + 2 ] … … a [ r ] ) gcd(a[l],a[l+1],a[l+2]……a[r]) gcd(a[l],a[l+1],a[l+2]a[r])

首先思考维护哪些信息

struct Node{
	int l,r;
	LL g;//gcd
}

只是查询的话已经够了,接下来思考修改怎么办。
如果是做区间修改的话很麻烦,我们可以利用差分把区间修改转为单点修改。
( x , y , z ) = ( x , y − x , z − y ) (x,y,z)=(x,y-x,z-y) (x,y,z)=(x,yx,zy)
( a 1 , a 2 , … … a n ) = ( a 1 , a 2 − a 1 , a 3 − a 2 , … … a n − a n − 1 ) (a_1,a_2,……a_n)=(a_1,a_2-a_1,a_3-a_2,……a_n-a_{n-1}) (a1,a2,an)=(a1,a2a1,a3a2,anan1)
g c d ( a [ l ] , a [ l + 1 ] , … … a [ r ] ) = g c d ( a [ l ] , b [ l + 1 ] , b [ l + 2 ] , b [ r ] ) gcd(a[l],a[l+1],……a[r])=gcd(a[l],b[l+1],b[l+2],b[r]) gcd(a[l],a[l+1],a[r])=gcd(a[l],b[l+1],b[l+2],b[r])
所以我们输入a[n]后弄出差分序列b[n]
问题变成了对于差分序列b[n]进行如下操作
操作1: b [ l ] + d , b [ r + 1 ] − d b[l]+d,b[r+1]-d b[l]+d,b[r+1]d
操作2:求 [ l , r ] [l,r] [l,r]最大公约数
g c d ( a [ l ] , g c d ( b [ l + 1 ] , … … b [ r ] ) ) gcd(a[l],gcd(b[l+1],……b[r])) gcd(a[l],gcd(b[l+1],b[r]))
可以发现a[l]也就是b数组的前缀和所以我们还要维护一个sum

struct Node{
	int l,r;
	LL g;//gcd
	LL sum;//sum
}

区间查询的答案就是 a n s = a b s ( g c d ( s u m [ 1 , l ] , g c d [ l + 1 , r ] ) ) ans=abs(gcd(sum_{[1,l]},gcd_{[l+1,r]})) ans=abs(gcd(sum[1,l],gcd[l+1,r]))

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,m;
LL a[N],b[N];
struct Node{
    int l,r;
    LL g;
    LL sum;
}tr[N*4];
/*维护更新信息*/
void pushup(Node &u,Node &l,Node &r){
    u.g=__gcd(l.g,r.g);
    u.sum=l.sum+r.sum;
}

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)tr[u]={l,r,b[r],b[r]};//叶子节点
    else{
        tr[u]={l,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)tr[u]={x,x,tr[u].sum+v,tr[u].sum+v};//叶子节点
    else{
        int mid=(tr[u].l+tr[u].r)>>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}
/*因为查询也有很多信息要合并所以直接传结构体方便些*/
Node query(int u,int l,int r){
    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 left=query(u<<1,l,r);
            auto right=query(u<<1|1,l,r);
            Node res;
            pushup(res,left,right);//合并左右子区间答案
            return res;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)b[i]=a[i]-a[i-1];
    build(1,1,n);
    
    int l,r;LL d;
    char op[2];
    while(m--){
        scanf("%s%d%d",op,&l,&r);
        if(*op=='Q'){
            auto left=query(1,1,l);
            Node right({0,0,0,0});
            if(l+1<=r)right=query(1,l+1,r);
            printf("%lld\\n",abs(gcd(left.sum,right.g)));
        }
        else{
            scanf("%lld",&d);
            modify(1,l,d);
            if(r+1<=n)modify(1,r+1,-d);
        }
    }
    return 0;
}

以上是关于[线段树 差分 区间转单点]区间最大公约数的主要内容,如果未能解决你的问题,请参考以下文章

线段树(单点修改,区间求和,区间最大)

线段树数组差分区间最大公约数

ACM入门之线段树习题

线段树单点修改区间修改单点查询值区间查询最大值最小值区间和之模板

HDU - 1754 线段树-单点修改+询问区间最大值

[LuoguP1438]无聊的数列(差分+线段树/树状数组)