acwing 246 区间最大公约数

目录

题目描述

思路

进一步探讨

更进一步探讨

题目链接

题目描述

给定一个长度为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)。

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

思路

首先可以很容易的想到用线段树维护区间GCD,确实是这样的,但是有一个问题,就是区间更新的时候,因为GCD不满足区间可加性,所有不能直接区间修改,只能对区间内每一个点进行单点修改,但是由于数据量庞大,会TLE,因此有了下面的超时代码

// TLE
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
#pragma warning (disable:4996)
#pragma warning (disable:6031)
using namespace std;
typedef long long ll;
int cnt;
ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}
const int N = 501000;
struct p {
    ll l, r, gcdsum;
} c[N * 4];
void build(int l, int r, int k) {
    c[k].l = l;
    c[k].r = r;
    if (l == r) {
        scanf("%lld", &c[k].gcdsum);
        return;
    }
    int mid = (l + r) / 2;
    build(l, mid, k * 2);
    build(mid + 1, r, k * 2 + 1);
    c[k].gcdsum = gcd(c[k * 2].gcdsum, c[k * 2 + 1].gcdsum);
}
void update(int ind, int k, ll d) {
    if (c[k].l == c[k].r) {
        c[k].gcdsum += d;
        return;
    }
    int mid = (c[k].l + c[k].r) / 2;
    if (ind <= mid)update(ind, k * 2, d);
    else update(ind, k * 2 + 1, d);
    c[k].gcdsum = gcd(c[k << 1].gcdsum, c[k << 1 | 1].gcdsum);
}
ll query(int l, int r, int k) {
    if (c[k].l >= l && c[k].r <= r) {
        return c[k].gcdsum;
    }
    int mid = (c[k].l + c[k].r) / 2;
    ll res = -1;
    if (l <= mid) {
        if (res == -1) {
            res = query(l, r, k * 2);
        }
        else res = gcd(res, query(l, r, k * 2));
    }
    if (r > mid) {
        if (res == -1) {
            res = query(l, r, k * 2 + 1);
        }
        else res = gcd(res, query(l, r, k * 2 + 1));
    }
    return res;
}
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    build(1, n, 1);
    while (m--) {
        char s[10];
        int x, y;
        ll z;
        scanf("%s %d %d", s, &x, &y);
        if (s[0] == 'C') {
            scanf("%lld", &z);
            for (int i = x; i <= y; i++) {
                update(i, 1, z);
            }
        }
        else {
            printf("%lld\n", query(x, y, 1));
        }
    }
    return 0;
}

进一步探讨

因为上面的思路不能AC,所以需要改进。超时主要发生在区间修改的位置,因此应想方设法降低更新次数(又用到了强大的数论)。

九章算术中有记载GCD的求解方法--更相减损术,过程如下:

  1. 如果两个数都是偶数,则同时除2,直到双发不同时为偶数为止,并记录除数的乘积(除掉了多少个2,求幂)

  2. GCD(a, b) = GCD(b, a - b) (a > b)直到被减数和差相等为止(用大的减去小的,保留小的)

由以上过程可以得出,交换GCD中a, b顺序可得,GCD(b, a) = GCD(b, a - b)

以上结论可以推广为多项GCD:GCD(A, B, C, D) = GCD(A, B - A, C - B, D - C)

也就是说,多项的GCD,等于第一项和后面的差分序列的GCD值

那么对于一个区间 [ i,j ] 内的GCD,可以通过GCD(A[i],query(i + 1,j))来实现(query是线段树维护的差分数组中查询区间gcd的函数)

而更新操作,只需要更新一下差分数组的两个端点即可,但是由于我们需要A[ i ]的值,因此还需要用树状数组维护一下差分数组的前缀和,每次更新,同时更新线段树和树状数组对应的两个端点即可

因此有了下列代码

// WA
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
#pragma warning (disable:4996)
#pragma warning (disable:6031)
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}
const int N = 501000;
struct p {
    ll l, r, gcdsum;
} c[N * 4];
ll a[N], b[N], t[N];
int cnt, n, m;
void build(int l, int r, int k) {
    c[k].l = l;
    c[k].r = r;
    if (l == r) {
        //scanf("%lld", &c[k].gcdsum);
        c[k].gcdsum = b[++cnt];
        return;
    }
    int mid = (l + r) / 2;
    build(l, mid, k * 2);
    build(mid + 1, r, k * 2 + 1);
    c[k].gcdsum = gcd(c[k * 2].gcdsum, c[k * 2 + 1].gcdsum);
}
void update(int ind, int k, ll d) {
    if (c[k].l == c[k].r) {
        c[k].gcdsum += d;
        return;
    }
    int mid = (c[k].l + c[k].r) / 2;
    if (ind <= mid)update(ind, k * 2, d);
    else update(ind, k * 2 + 1, d);
    c[k].gcdsum = gcd(c[k << 1].gcdsum, c[k << 1 | 1].gcdsum);
}
ll query(int l, int r, int k) {
    if (c[k].l >= l && c[k].r <= r) {
        return c[k].gcdsum;
    }
    int mid = (c[k].l + c[k].r) / 2;
    ll res = -1;
    ll ans1 = 0;
    ll ans2 = 0;
    if (l <= mid) {
        ans1 = query(l, r, k * 2);
    }
    if (r > mid) {
        ans2 = query(l, r, k * 2 + 1);
    }
    res = (gcd(ans1, ans2));
    return res;
}
template <class T>
T lowbit(T x) {
    return x & -x;
}
template <class T, class R>
void add(T x, R d) {
    while (x <= n) {
        t[x] += d;
        x += lowbit(x);
    }
}
template <class T>
ll query(T x) {
    ll res = 0;
    while (x) {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}
int main()
{
    scanf("%d %d", &n, &m);
    cnt = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
    }
    for (int i = 2; i <= n; i++) {
        b[i] = a[i] - a[i - 1];
    }
    mem(t, 0);
    build(1, n, 1);
    while (m--) {
        char s[10];
        int x, y;
        ll z;
        scanf("%s %d %d", s, &x, &y);
        if (s[0] == 'C') {
            scanf("%lld", &z);
            add(x, z);
            add(y + 1, -z);
            update(x, 1, z);
            if(y + 1 <= n)update(y + 1, 1, -z);
        }
        else {
            ll res = query(x + 1, y, 1);
            res = gcd(res, a[x] + query(x));
            printf("%lld\n", res);
        }
    }
    return 0;
}

更进一步探讨

WA不是因为代码写错了,是因为有一些细节没处理好。GCD值是不允许有负数的,我们就要想办法处理负数。(又需要数论)

GCD(a,-b) = GCD(a,b)

有了这么一条结论就可以轻松处理负数了,一旦遇到负数,就取反处理

但是,这个结论只能用于计算,也就是说不可以直接在更新的时候把GCD的值,更新成正的,会影响后面的计算结果,所以可以在query函数中在返回值的位置,将GCD的值取绝对值返回出来就可以了

又有了下列代码

#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
#pragma warning (disable:4996)
#pragma warning (disable:6031)
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}
const int N = 501000;
struct p {
    ll l, r, gcdsum;
} c[N * 4];
ll a[N], b[N], t[N];
int cnt, n, m;
void build(int l, int r, int k) {
    c[k].l = l;
    c[k].r = r;
    if (l == r) {
        c[k].gcdsum = b[++cnt];
        return;
    }
    int mid = (l + r) / 2;
    build(l, mid, k * 2);
    build(mid + 1, r, k * 2 + 1);
    c[k].gcdsum = gcd(c[k * 2].gcdsum, c[k * 2 + 1].gcdsum);
}
void update(int ind, int k, ll d) {
    if (c[k].l == c[k].r) {
        c[k].gcdsum += d;
        return;
    }
    int mid = (c[k].l + c[k].r) / 2;
    if (ind <= mid)update(ind, k * 2, d);
    else update(ind, k * 2 + 1, d);
    c[k].gcdsum = gcd(c[k << 1].gcdsum, c[k << 1 | 1].gcdsum);
}
ll query(int l, int r, int k) {
    if (c[k].l >= l && c[k].r <= r) {
        return c[k].gcdsum;
    }
    int mid = (c[k].l + c[k].r) / 2;
    ll res = -1;
    ll ans1 = 0;
    ll ans2 = 0;
    if (l <= mid) {
        ans1 = query(l, r, k * 2);
    }
    if (r > mid) {
        ans2 = query(l, r, k * 2 + 1);
    }
    // 改动了的地方
    res = (gcd((ll)abs(ans1), (ll)abs(ans2)));
    // 
    return res;
}
template <class T>
T lowbit(T x) {
    return x & -x;
}
template <class T, class R>
void add(T x, R d) {
    while (x <= n) {
        t[x] += d;
        x += lowbit(x);
    }
}
template <class T>
ll query(T x) {
    ll res = 0;
    while (x) {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}
int main()
{
    scanf("%d %d", &n, &m);
    cnt = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
    }
    for (int i = 2; i <= n; i++) {
        b[i] = a[i] - a[i - 1];
    }
    mem(t, 0);
    build(1, n, 1);
    while (m--) {
        char s[10];
        int x, y;
        ll z;
        scanf("%s %d %d", s, &x, &y);
        if (s[0] == 'C') {
            scanf("%lld", &z);
            add(x, z);
            add(y + 1, -z);
            update(x, 1, z);
            if(y + 1 <= n)update(y + 1, 1, -z);
        }
        else {
            ll res = query(x + 1, y, 1);
            res = gcd(res, a[x] + query(x));
            printf("%lld\n", res);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值