ACwing 区间最大公约数题解 线段树(附证明)
https://www.acwing.com/problem/content/247/
题目:
给定一个长度为 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 条指令,每条指令的格式如题目描述所示。
输出格式:
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围:
N≤500000,M≤100000, 1≤A[i]≤10^{18}, |d|≤10^{18}
样例:
data.1
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
data.2
5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 5 5
C 3 3 6
Q 2 4
1
10
4
思路:
首先区间修改,考虑线段树,分析如何处理怎么维护区间的 gcd,容易想到: gcd(a,b,c)=gcd(gcd(a,b),gcd(b,c)) ,但是由于存在区间修改,我们需要处理如何从 gcd(a,b,c) 推导出 gcd(a+1,b+1,c+1)?结论是利用差分,因为有 gcd(a,b)=gcd(a,b-a),所以有:
证明:
证明 gcd(a,b) = gcd(a,b-a):
令gcd(a,b)=g,所以 \left\{\begin{matrix} a=k_1g \\ b=k_2g \end{matrix}\right. ,且 k_1 与 k_2 互质,所以 b=(k_2-k_1)g,若证 gcd(a,b)=gcd(a,b-a),即证:gcd(a,b-a)=g=gcd(a,b),即证:k_1 与 (k_2-k_1) 互质。
假设 k_1 与 (k_2-k_1) 不互质,存在最大公因数 g',g'≠1,则有:\left\{\begin{matrix} k_1=xg' \\ k_2-k_1=yg' \end{matrix}\right. 。
即 (k_2-k_1)g'+k_1g'=(x+y)g'=k_2,那么有 gcd(k_1,k_2)=g', g'≠1,则 k_1⊥(k_2-k_1), 那么有 gcd(a,b-a)=g=gcd(a,b) 成立。
证明 gcd(a,b,c)=gcd(a,b-a,c-b)
用 (a,b) 表示 gcd(a,b)。
线段树 tr 对差分进行维护,我们想要修改原数组中 A[l,r],都加上d,就相当于在差分数组上 b[l]+=d,b[r+1]-=d,对应线段树就是 modify(1, l, d),以及 modify(1, r+1, -d),需要注意比较 r+1 和线段树边界 N 的大小,若 r+1>N ,则直接不需要操作。 由于 gcd(a,b,c)=gcd(a,b-a,c-b),所以我们需要知道第一个的真实值,即对差分数组求前缀和,对应线段树操作就是 askSum(1,1,l),并且和 [l+1,r] 求最大公约数 gcd,即 askGcd(1,l+1,r),因为差分操作使得 gcd 存在负数,则是不符合的,所以输出需要取绝对值。
由数据范围知需要开 long long。
pushup 的实现:
void pushup(seg &u, seg &l, seg &r) {
u.sum = l.sum+r.sum;
u.gcd = __gcd(l.gcd, r.gcd);
}
Coding 时间到
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
constexpr int N = 5e5 + 13;
struct seg{
int l, r;
int sum, gcd;
}tr[N*4];
int n, a[N], b[N];
void pushup(seg &u, seg &l, seg &r) {
u.sum = l.sum+r.sum;
u.gcd = __gcd(l.gcd, r.gcd);
}
void pushup(int u) {
pushup(tr[u], tr[u<<1], tr[u<<1|1]);
}
void build(int u, int l, int r) {
tr[u].l=l, tr[u].r=r;
if (l==r) tr[u].sum=tr[u].gcd=b[l];
else {
int mid=l+r>>1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
void modify(int u, int pos, int x) {
if (pos == tr[u].l && tr[u].r == pos) {
tr[u].sum += x, tr[u].gcd += x;
} else {
int mid = tr[u].l+tr[u].r>>1;
if (pos <= mid) modify(u<<1, pos, x);
else modify(u<<1|1, pos, x);
pushup(u);
}
}
int askSum(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].sum;
}
int mid = tr[u].l+tr[u].r>>1;
int ans = 0;
if (l <= mid) ans+=askSum(u<<1, l, r);
if (r > mid) ans+=askSum(u<<1|1, l, r);
return ans;
}
int askGcd(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].gcd;
}
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return askGcd(u<<1, l, r);
else if (l > mid) return askGcd(u<<1|1, l, r);
else return __gcd(askGcd(u<<1,l,r), askGcd(u<<1|1,l,r));
}
int solve()
{
int q; cin >> n >> q;
for (int i=1;i <= n;i ++ ) {
cin>>a[i]; b[i]=a[i]-a[i-1];
}
build(1, 1, N-3);
// for (int i=1;i <= n;i ++ )
// cout<<askSum(1, 1, i)<<" \n"[i==n];
for (int i=1;i <= q;i ++ ) {
string op; cin >> op;
if (op[0] == 'C') {
int l, r, d; cin >> l >> r >> d;
modify(1, l, d);
if (r+1 < N) modify(1, r+1, -d);
} else {
int l, r; cin >> l >> r;
int step = askSum(1, 1, l);
int g = (r-l)?__gcd(step, askGcd(1, l+1, r)):step;
cout << abs(g) << '\n';
}
// cout<<"i:? "<<'\n';
// for (int i=1;i <= n;i ++ )
// cout<<askSum(1,1,i)<<" \n"[i==n];
}
return 1;
}
signed main()
{
cin.tie(0) -> sync_with_stdio(0);
int T(1); //cin>>T;
while (T -- )
solve();
return 0;
}

浙公网安备 33010602011771号