维护区间修改和区间最大公约数(线段树)
题目大意:要我们对一段区间进行两种操作:\(1.\)区间加法,\(2.\)查询区间\(\gcd\)。
看到有区间修改的操作,首先考虑用线段树,这样可以方便的维护区间修改的操作,但是区间\(\gcd\)是会随着区间修改而时刻变化的,比如:区间为\(2, 4, 6\),此时的区间\(\gcd\)是\(2\),但是如果给这个区间的每一个数都加上一个\(1\),就变成了\(3, 5, 7\)此时的区间\(\gcd\)为\(1\).所以很难直接维护。但是呢,\(\gcd\)是有一个很重要的性质的,就是$$\gcd(a,b) = \gcd(a - b, b)$$.
就因为这个结论,我们就可以把区间\(\gcd(a, b, c, d)\)转化为\(\gcd(a, b, c - d, d) , \gcd(a, b - c, c - d, d), \gcd(a - b, b - c, c - d, d)\)最后我们要维护的区间\(\gcd\)也就成了维护一个原数组的一个差分数组,同时我们知道差分数组最后前面的\(x\)项全部加起来才是原来的\(x\)项,所以我们这个时候也可以把区间修改操作转化成对这个区间的两个端点进行单点修改的操作,也就是左端点加\(x\),右端点\(-x\)抵消掉它们对其他区间的影响.
struct SegmentTree {
int val[N << 2];
int gcd[N << 2];
void pushup(int u) {
val[u] = val[u << 1] + val[u << 1 | 1];
gcd[u] = std::__gcd(gcd[u << 1], gcd[u << 1 | 1]);
}
void build(int u, int l, int r) {
if (l == r) { val[u] = a[l], gcd[u] = a[l]; return ;}
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(int u, int l, int r, int x, int v) {
if (l == r) {
val[u] += v;
gcd[u] += v;
return ;
}
int mid = l + r >> 1;
if (x <= mid) modify(u << 1, l, mid, x, v);
else modify(u << 1 | 1, mid + 1, r, x, v);
pushup(u);
}
int GCD(int u, int l, int r, int ln, int rn) {
if (ln <= l && rn >= r) return gcd[u];
int mid = l + r >> 1;
if (rn <= mid) return GCD(u << 1, l, mid, ln, rn);
else if (ln > mid) return GCD(u << 1 | 1, mid + 1, r, ln, rn);
else return std::__gcd(GCD(u << 1, l, mid, ln, rn) , GCD(u << 1 | 1, mid + 1, r, ln, rn));
}
int query(int u, int l, int r, int ln, int rn) {
if (ln <= l && rn >= r) return val[u];
int mid = l + r >> 1;
if (rn <= mid) return query(u << 1, l, mid, ln, rn);
else if (ln > mid) return query(u << 1 | 1, mid + 1, r, ln, rn);
else return query(u << 1, l, mid, ln, rn) + query(u << 1 | 1, mid + 1, r, ln, rn);
}
}SGT;

浙公网安备 33010602011771号