题目
给定一个长度为 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 (a, b) = gcd (a, b-a);同理我们可以推导一下发现 gcd (a, b, c) = gcd (a, b-a, c-b) (例如:gcd (4, 6, 12) = gcd (4, 2, 6)),同时这个性质对任意多整数都成立。那么我们使用线段树来完成:维护线段树Mem,使得每一个节点代表着一个区间的最大公约数。这个线段树的叶节点 p 代表 A[p] - A[p-1],这样我们可以利用 gcd 的性质将区间修改转化为单点修改。此时查询命令变为 gcd(A[l], query(1, l+1, r));修改变为:change(1, l, d) 和change(1, r+1, -d)。
对于数组A,我们可以使用“区间修改,单点查询”的树状数组维护。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 500005
long long gcd(ll a, ll b) {
while (a != 0 && b != 0) {
ll t = a;
a = b % t;
b = t;
}
return a + b;
}
int n, m, N=MAXN;
ll a[MAXN];
ll treemem[MAXN]; //树状数组维护的是每一次更新的内容
int lowbit (int x) {
return x & (-x);
} //找到x二进制下等于1的最低位
void update (int l, int r, ll val) { //l到r区间加val转化为两次单点修改
for (int i=l; i<=N; i+=lowbit(i)) {
treemem[i] += val;
}
for (int i=r+1; i<=N; i+=lowbit(i)) {
treemem[i] -= val;
}
}
ll query (int pos) { //取出pos位置的值
ll ans = a[pos];
for (int i=pos;i>0;i-=lowbit(i)) {
ans+=treemem[i];
}
return ans;
}
struct Mem { //线段树
int l, r;
ll val;
} mem[MAXN*4];
void UpdateSegtree (int p) { //每个节点记录的都是最大公约数
mem[p].val = gcd(mem[p*2].val, mem[p*2+1].val);
}
void BuildSegtree (int p, int l, int r) { //建立线段树
mem[p].l = l; mem[p].r = r;
if (mem[p].l == mem[p].r) {
mem[p].val = a[l] - a[l-1]; //线段树的叶节点值为A数组两项之差
return;
}
int mid = (l+r)/2;
BuildSegtree(p*2, l, mid);
BuildSegtree(p*2+1, mid+1, r);
UpdateSegtree(p);
}
ll QuerySegtree (int p, int l, int r) { //查询函数
if (l<=mem[p].l && mem[p].r<=r) { return mem[p].val; }
int mid = (mem[p].l+mem[p].r)/2;
if (r <= mid) {
return QuerySegtree(p*2, l, r);
}
if (mid < l) {
return QuerySegtree(p*2+1, l, r);
}
return gcd(QuerySegtree(p*2, l, mid), QuerySegtree(p*2+1, mid+1, r));
}
void PchangeSegtree (int p, int x, ll v) { //单点修改
if (mem[p].l == mem[p].r) {
mem[p].val += v;
return;
}
int mid = (mem[p].l+mem[p].r)/2;
if (x <= mid) PchangeSegtree(p*2, x, v);
else PchangeSegtree(p*2+1, x, v);
UpdateSegtree(p);
}
int main () {
while (scanf("%d %d", &n, &m) != EOF) {
for (int i=1; i<=n; i++) {
scanf("%lld", &a[i]);
}
BuildSegtree(1, 1, n+1);
for (int i=0; i<m; i++) {
char inps[2];
int x, y;
ll z;
scanf("%s", inps);
if (inps[0] == 'Q') {
scanf("%d %d", &x, &y);
if (x == y) printf("%lld\n", abs(query(x)));
else printf("%lld\n", abs(gcd(query(x), QuerySegtree(1, x+1, y))));
}
else {
scanf("%d %d %lld", &x, &y, &z);
PchangeSegtree(1, x, z); //对线段树的修改
PchangeSegtree(1, y+1, -z); //因为类似差分数列,所以只修改两个位置的值
update(x, y, z); //对树状数组的修改
}
}
}
}
上传前尝试过了,能Accept。