• AcWing 246. 区间最大公约数


    \(AcWing\) \(246\). 区间最大公约数

    一、题目描述

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

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

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

    输入格式
    第一行两个整数 \(N,M\)

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

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

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

    每个答案占一行。

    二、解题思路

    1、差分数组

    因为涉及到 区间修改 的问题,直观上需要 \(pushdown\) 操作,即 用父节点信息来更新子节点信息,但是 \(pushdown\) 操作很复杂并且容易写错(懒标记啥的,费劲~),所以 使用差分数组的技巧, 转区间修改为单点修改:

    设原数组为\(\large a[i]\),对应的差分数组 \(\large b[i]\)\(\large b_i=a_i−a_{i−1}\),(这里认为\(a[0]=0\)

    那么线段树维护这个\(\large b\)数组就可得到 单点修改从而改变整个区间 的效果。

    2、更相减损术

    那么根据 更相减损术

    则有:\(\large gcd(a,b)=gcd(a,b−a)\)

    动动脑筋推广一下得:【动完脑筋还是没想明白,那就直接背结论吧~

    \[\large gcd(a,b,c)=((a,b),(b,c))=(a,b−a,c−b) \]

    我们想要求的就是:\(\large (A_l,A_{l+1},A_{l+2}…A_r)\)这个区间的 最大公约数

    根据上面的 更相减损数理论,就是在求下面区间的 最大公约数:

    \[\large (A[l],A[l+1]−A[l],A[l+2]−A[l+1],A[l+3]−A[l+2],…,A[r]−A[r−1]) \]

    稍微转化一下,得到:

    \[\large (A[l],b[l+1],b[l+2],b[l+3],…,b[r]) \]

    这个东西要一分两半来看:

    • \(A[l]\):因为我们维护的是一个差分数组的线段树,所以可以转化为差分的写法:

      \[\large A[l]=sum(b[1],b[2],...,b[l]) \]

    • ② 后面的那一坨

      \[\large (b[l+1],b[l+2],b[l+3],…,b[r]) \]

      就是区间\(l+1 \sim r\)的最大\(gcd\)值,这个东西在线段树的节点上以结构体形式保存着呢,可以直接Node right=query(1,l+1,r)查询出来,\(right.d\)就是最大公约数

    • ③ 最后两者打一下擂台:
      \(\large (A_l,A_{l+1},A_{l+2}…A_r)\)这个区间的最大公约数,就是

    res=abs(gcd(left.sum,right.d))
    

    3、负数的最大公约数

    注意\(gcd\)操作是没有负数的,所以需要进行取反。

    三、实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    const int N = 500010;
    
    int n, m;
    LL w[N];
    
    struct Node {
        int l, r;
        LL sum; //区间总和
        LL d;   //区间内的最大公约数
    } tr[N << 2];
    
    //求最大公约数
    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]; //更相减损数,所以按原数组差分构建,yxc大佬很良心修改了试题,添加了1e18的数据范围说明
            tr[u] = {l, r, b, b};   //当是叶子节点时,区间和就是自己,区间最大公约数也是自己
            return;
        }
        tr[u] = {l, r}; //不加这句就和yxc一样的下场~,只对结构体的前两个属性进行赋值
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        //子节点变更需要更新父节点需要更新父节点的总和、最大公约数
        pushup(u);
    }
    //以u为根的子树中,修改位置为x的节点,值为+v
    void modify(int u, int x, LL v) {
        if (tr[u].l == tr[u].r) {           //找到叶子节点
            LL b = tr[u].sum + v;           //+v
            tr[u].sum = tr[u].d = b;        //修改区间和与最大公约数
            return;
        }
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid)                 //在左侧
            modify(u << 1, x, v);     //让左儿子处理
        else                          //在右侧
            modify(u << 1 | 1, x, v); //让右儿子处理
        // u的子节点数据变更,需要从u开始向上更新父节点信息
        pushup(u);
    }
    
    //查询
    Node query(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) return tr[u];
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        if (l > mid) return query(u << 1 | 1, l, r);
        Node left = query(u << 1, l, r);
        Node right = query(u << 1 | 1, l, r);
        Node res;
        pushup(res, left, right);
        return res;
    }
    
    int main() {
        //加快读入
        ios::sync_with_stdio(false), cin.tie(0);
    
        cin >> n >> m;
        for (int i = 1; i <= n; i++) cin >> w[i];
    
        //因为差分的r+1可能越界,这里在建立线段树时就多创建一个位置就OK!
        build(1, 1, n + 1);
    
        int l, r;
        LL d;
        char op;
        while (m--) {
            cin >> op >> l >> r;
            if (op == 'Q') {
                Node left = query(1, 1, l);
                Node right({0, 0, 0, 0});
                if (l < r) right = query(1, l + 1, r); //如果区间内只有一个点l=r,则直接返回a[l]即可,即left.sum, right为空
                printf("%lld\n", abs(gcd(left.sum, right.d)));
            } else {
                cin >> d;
                modify(1, l, d), modify(1, r + 1, -d);
            }
        }
        return 0;
    }
    
  • 相关阅读:
    C语言I博客作业11
    第十四周助教总结
    C语言I博客作业10
    Tensorflow--MNIST简单全连接层分类
    记一些好看的Android开源菜单
    AS更新到3.5.2遇到ERROR: SSL peer shut down incorrectly问题
    Tensorflow tf.app.flags 的使用
    Android Sensor(传感器)
    《第一行代码》百分比布局出现的问题
    当Turtle遇见柯南?
  • 原文地址:https://www.cnblogs.com/littlehb/p/16152939.html
Copyright © 2020-2023  润新知