目录
题目描述
给定一个长度为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的求解方法--更相减损术,过程如下:
-
如果两个数都是偶数,则同时除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;
}