题意:
一个n个数的序列。对它进行 3 种操作。
1 l r:输入a[l,r]的和
2 l r x:令[l,r]所有数对x取模
3 k x:令a[k] = x
每到操作1时输出和。
(1 ≤ n, m ≤ 1e5). (1 ≤ a[i] ≤ 1e9)
分析:
看到区间更新,应该想到懒惰标记。但是用懒惰标记应该满足两个条件:
- 标记可以合并
- 可以快速更新区间信息。
但是这道题不满足条件2,也就是无法快速更新区间和。而逐个更新每个数取模,仿佛又太慢了。
正确解法基于一个重要的结论,a % b <= a/2,这就意味着,任何一个数loga[i]次取模以内就能变为0。同时还注意到,a % b = a (a < b)。这两点便可以极大的降低时间复杂度。
至此,便得到解题思路。单点更新和区间和并无特殊之处,而对于每个区间取模操作,如同遍历这个区间所有的数字一样,逐个取模更新。但是若某个区间的区间最大值小于mod,那么这个区间就无需有任何操作。这相当于一个剪枝。所以我们需要两个数组,一个记录区间和,一个记录区间最大值。总的时间复杂度为O(nlognloga[i])。
其实线段树的实质就是一个dfs搜索树的过程,只不过在每次搜索的时候都判断区间是否重合、是否相交、区间某值是否满足条件等等,这些过程的实质就是剪枝。时间复杂度就是靠这些剪枝降到logn的,否则就像遍历一棵树一样是O(n)。
代码
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
#define lson rt*2,l,(r+l)/2
#define rson rt*2+1,(l+r)/2+1,r
const int MAXN = 1e5 + 5;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
ll mx[MAXN << 2], sum[MAXN << 2];
int n, m;
void pushup(int rt) {
mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void build(int rt, int l, int r) {
if (l == r) {
scanf("%lld", &mx[rt]);
sum[rt] = mx[rt];
return;
}
build(lson);
build(rson);
pushup(rt);
}
ll query(int L, int R, int rt, int l, int r) {
if (L <= l && R >= r) {
return sum[rt];
}
if (R <= (l + r) / 2) return query(L, R, lson);
else if (L > (l + r) / 2) return query(L, R, rson);
else return query(L, R, lson) + query(L, R, rson);
}
void update(int L, int R, int rt, int l, int r, int mod) {
if (L <= l && R >= r) {
if (mx[rt] < mod) return;
if (l == r) {
mx[rt] = sum[rt] = mx[rt] % mod;
return;
}
}
if (L <= (l + r) / 2) update(L, R, lson, mod);
if (R > (l + r) / 2) update(L, R, rson, mod);
pushup(rt);
}
void change(int rt, int l, int r, int k, int x) {
if (l == r) {
mx[rt] = x;
sum[rt] = x;
return;
}
if (k <= (l + r) / 2) change(lson, k, x);
else change(rson, k, x);
pushup(rt);
}
int main() {
while (~scanf("%d%d", &n, &m)) {
build(1, 1, n);
while (m--) {
int op, L, R, k, x;
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &L, &R);
printf("%lld\n", query(L, R, 1, 1, n));
} else if (op == 2) {
scanf("%d%d%d", &L, &R, &x);
update(L, R, 1, 1, n, x);
} else {
scanf("%d%d", &k, &x);
change(1, 1, n, k, x);
}
}
}
return 0;
}