线段树(求区间最大公约数)

该文章描述了一个程序设计问题,给定一个数列和一系列指令,包括对数列部分元素的加法操作以及查询连续子序列的最大公约数。解决方案使用了二叉树结构来高效处理这些操作,其中二叉树节点包含子序列的和与最大公约数信息,支持快速查询和更新。
摘要由CSDN通过智能技术生成

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
  2. Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。

对于每个询问,输出一个整数表示答案。

输入格式

第一行两个整数 N,M。

第二行 N 个整数 A[i]。

接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。

输出格式

对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围

N≤500000,M≤100000
1≤A[i]≤1018
|d|≤1018

输入样例:

5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 1 5
C 3 3 6
Q 2 4

输出样例:

1
2
4

#include <iostream>

using namespace std;
typedef long long ll;
constexpr int N=500010;
int n,m;
ll w[N];
struct node{
    int l,r;
    ll sum,d;
}tr[N*4];
ll gcd(ll a,ll b){
    return b? gcd(b,a%b):a;
}
void pushup(node& u,node& l,node& r){
    u.sum=l.sum+r.sum;
    u.d= gcd(l.d,r.d);
}
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=w[r]-w[r-1];
        tr[u]={l,r,b,b};
    }
    else{
        tr[u].l=l,tr[u].r=r;
        int mid=l+r>>1;
        build(u<<1|1,mid+1,r);
        build(u<<1,l,mid);
        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 modify(u<<1|1,x,v);
        pushup(u);
    }
}
node query(int u,int l,int r){
    if(l>r) return {0};
    if(tr[u].l>=l&&tr[u].r<=r) return tr[u];
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(l>mid) return query(u<<1|1,l,r);
        else if (r<=mid)return query(u<<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++) cin>>w[i];
    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);
            auto right=query(1,l+1,r);
            printf("%lld\n",abs(gcd(left.sum,right.d)));
        }
        else
        {
            scanf("%lld",&d);
            modify(1,l,d);
            if(r+1<=n) modify(1,r+1,-d);
        }
    }

    return 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
发出的红包

打赏作者

q619718

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

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

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

打赏作者

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

抵扣说明:

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

余额充值