線段樹區間取模(The Child and Sequence)
阿新 • • 發佈:2021-07-28
題面
題解
區間和和單點修改是我們熟悉的。
但是對於第二種區間取模操作,我們不難發現,如果按照類似於區間加,維護一個懶標記的話,是很難維護的,因為它很不好合並。
如果做過花神遊歷各國,可以類比一下區間開方的操作,暴力修改。
但是區間開方開個幾次就變成 \(0\) 或 \(1\) 了,區間取模是否具有類似的性質呢?
對於數 \(x\), \(x = x \pmod p(x < p)\), \(x < \frac{x}{2} \pmod p(x > p)\)。
對於式一顯然成立。
對於式二,當 \(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; }