区间最大公约数

线段树练习

思路:

这是一个区间加一个数,和查询区间最大公约数的问题,这里按理说涉及到区间改了,线段树的话就需要加上懒标记了,但是我们通过分析知道这里只是区间整体加一个数或者整体减一个数,这里我们思考一个问题


区间 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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
我们可以使用线段树来解决这个问题。对于每个区间,我们都可以预处理出其内部所有数的最大公约数,然后在询问时,查询覆盖该询问区间的所有区间最大公约数并取最大值即可。 具体地,我们可以将二维区间 $(i,j)$ 分别看作 $i$ 和 $j$ 两个维度,建立一颗二维线段树。对于每个节点 $(x,y)$,它表示的区间为 $[l_x,r_x]\times[l_y,r_y]$,其中 $l_x,r_x,l_y,r_y$ 分别表示该节点在 $x$ 和 $y$ 维度上的左右边界。我们可以在每个节点上维护一个值 $g_{x,y}$,表示区间 $[l_x,r_x]\times[l_y,r_y]$ 内部所有数的最大公约数。 对于每个节点 $(x,y)$,我们可以通过递归地计算其左右儿子节点的 $g$ 值来求出该节点的 $g$ 值。具体地,我们可以将节点 $(x,y)$ 表示的区间分成四个子区间,分别为 $[l_x,\lfloor\frac{l_x+r_x}{2}\rfloor]\times[l_y,\lfloor\frac{l_y+r_y}{2}\rfloor]$、$[\lfloor\frac{l_x+r_x}{2}\rfloor+1,r_x]\times[l_y,\lfloor\frac{l_y+r_y}{2}\rfloor]$、$[l_x,\lfloor\frac{l_x+r_x}{2}\rfloor]\times[\lfloor\frac{l_y+r_y}{2}\rfloor+1,r_y]$ 和 $[\lfloor\frac{l_x+r_x}{2}\rfloor+1,r_x]\times[\lfloor\frac{l_y+r_y}{2}\rfloor+1,r_y]$。然后我们可以递归地计算出这四个子区间的 $g$ 值,然后将它们合并起来得到该节点的 $g$ 值。合并方法为取四个子区间的 $g$ 值的最大公约数。 查询时,我们从根节点开始,递归地查找覆盖询问区间的节点,并将这些节点的 $g$ 值取最大值。具体地,对于当前节点 $(x,y)$,如果它表示的区间与询问区间不相交,则直接返回 1。否则,如果它表示的区间完全包含询问区间,则返回该节点的 $g$ 值。否则,我们将询问区间分成四个子区间,并递归地查询每个子区间,然后将它们的 $g$ 值取最大公约数作为当前节点的 $g$ 值返回。 时间复杂度为 $O((n+m)\log^2(n+m))$,其中 $n$ 和 $m$ 分别为二维区间的行数和列数。空间复杂度为 $O((n+m)\log^2(n+m))$。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向夕阳Salute

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值