线段树区间取模(The Child and Sequence)
题面
题解
区间和和单点修改是我们熟悉的。
但是对于第二种区间取模操作,我们不难发现,如果按照类似于区间加,维护一个懒标记的话,是很难维护的,因为它很不好合并。
如果做过花神游历各国,可以类比一下区间开方的操作,暴力修改。
但是区间开方开个几次就变成 \(0\) 或 \(1\) 了,区间取模是否具有类似的性质呢?
对于数 \(x\), \(x = x \pmod p(x < p)\), \(x < \frac{x}{2} \pmod p(x > p)\)。
对于式一显然成立。
对于式二,当 \(p < \frac{x}{2}\) 时,\(x \bmod p < p < \frac{x}{2}\),当 \(p > \frac{x}{2}\) 时,\(x \bmod p = x - p < \frac{x}{2}\)。
所以我们维护区间最大值,当最大值小于模数时,直接返回,当最大值大于模数时,暴力修改,因为每次区间最大值减半,所以暴力修改不超过 \(log\) 次,所以复杂度是有保障的。
代码
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, m, a[N];
struct SegmentTree {
#define M N << 2
int l[M], r[M], Max[M]; LL sum[M];
inline void pushup(int p) {
sum[p] = sum[p << 1] + sum[p << 1 | 1];
Max[p] = max(Max[p << 1], Max[p << 1 | 1]);
}
void build(int p, int L, int R) {
l[p] = L, r[p] = R;
if(L == R) {
sum[p] = Max[p] = a[L];
return ;
}
int mid = (L + R) >> 1;
build(p << 1, L, mid);
build(p << 1 | 1, mid + 1, R);
pushup(p);
}
void update(int p, int pos, int k) {
if(l[p] == r[p]) {
sum[p] = Max[p] = k;
return ;
}
int mid = (l[p] + r[p]) >> 1;
if(pos <= mid) update(p << 1, pos, k);
else update(p << 1 | 1, pos, k);
pushup(p);
}
void Mod(int p, int L, int R, int mod) {
if(Max[p] < mod) return ;
if(l[p] == r[p]) {
sum[p] %= mod, Max[p] %= mod;
return ;
}
int mid = (l[p] + r[p]) >> 1;
if(L <= mid) Mod(p << 1, L, R, mod);
if(R > mid) Mod(p << 1 | 1, L, R, mod);
pushup(p);
}
LL query(int p, int L, int R) {
if(L <= l[p] && r[p] <= R) return sum[p];
int mid = (l[p] + r[p]) >> 1; LL ans = 0;
if(L <= mid) ans += query(p << 1, L, R);
if(R > mid) ans += query(p << 1 | 1, L, R);
return ans;
}
}tr;
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
tr.build(1, 1, n);
for(int i = 1, opr, x, y, mod; i <= m; i++) {
scanf("%d%d%d", &opr, &x, &y);
if(opr == 1) printf("%lld\n", tr.query(1, x, y));
else if(opr == 2) {
scanf("%d", &mod);
tr.Mod(1, x, y, mod);
}
else tr.update(1, x, y);
}
return 0;
}

浙公网安备 33010602011771号