1.修改:用到了相应的空间就开,没有用到就不开。cnt拓展节点编号,此时各范围的节点编号不再按照i*2和i*2+1的对应关系建立
2.查询:
如果查询时一段范围没有建立过,就说明这段范围的累加和就是0
3.空间估计:一次操作最差的情况就是从顶点节点沿两条边界线一直扎到末尾,所以每一次增加操作开辟2*logn空间,一共m次操作,所以大致开辟2*m*logn
P2781 传教 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
和静态实现线段树的区别就是找左右点的编号的方式不同
java
// 动态开点线段树
// 一共有n个位置,编号从1~n,一开始所有位置的值为0
// 实现如下两个操作,一共会调用m次
// 操作 1 l r v : 把l~r范围的每个数增加v
// 操作 2 l r : 返回l~r范围的累加和
// 1 <= n <= 10^9
// 1 <= m <= 10^3
// 测试链接 : https://www.luogu.com.cn/problem/P2781
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Code01_DynamicSegmentTree {
// 范围1 ~ 10^9,线段树高度差不多30
// 查询次数1000,每次查询都有左右两条边线
// 所以空间占用差不多1000 * 30 * 2 = 60000
// 适当调大即可
public static int LIMIT = 80001;
public static int cnt;
public static int[] left = new int[LIMIT];
public static int[] right = new int[LIMIT];
public static long[] sum = new long[LIMIT];
public static long[] add = new long[LIMIT];
public static void up(int h, int l, int r) {
sum[h] = sum[l] + sum[r];
}
public static void down(int i, int ln, int rn) {
if (add[i] != 0) {
// 懒更新任务下发
// 那左右两侧的空间需要准备好
if (left[i] == 0) {
left[i] = ++cnt;
}
if (right[i] == 0) {
right[i] = ++cnt;
}
lazy(left[i], add[i], ln);
lazy(right[i], add[i], rn);
add[i] = 0;
}
}
public static void lazy(int i, long v, int n) {
sum[i] += v * n;
add[i] += v;
}
public static void add(int jobl, int jobr, long jobv, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
lazy(i, jobv, r - l + 1);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
// 不得不去左侧才会申请
if (left[i] == 0) {
left[i] = ++cnt;
}
add(jobl, jobr, jobv, l, mid, left[i]);
}
if (jobr > mid) {
// 不得不去右侧才会申请
if (right[i] == 0) {
right[i] = ++cnt;
}
add(jobl, jobr, jobv, mid + 1, r, right[i]);
}
up(i, left[i], right[i]);
}
}
public static long query(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return sum[i];
}
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
long ans = 0;
if (jobl <= mid) {
// 发现左侧申请过空间才有必要去查询
// 如果左侧从来没有申请过空间那查询结果就是0
if (left[i] != 0) {
ans += query(jobl, jobr, l, mid, left[i]);
}
}
if (jobr > mid) {
// 发现右侧申请过空间才有必要去查询
// 如果右侧从来没有申请过空间那查询结果就是0
if (right[i] != 0) {
ans += query(jobl, jobr, mid + 1, r, right[i]);
}
}
return ans;
}
// 如果一次会执行多组测试数组
// 那么每组测试完成后要clear空间
public static void clear() {
Arrays.fill(left, 1, cnt + 1, 0);
Arrays.fill(right, 1, cnt + 1, 0);
Arrays.fill(sum, 1, cnt + 1, 0);
Arrays.fill(add, 1, cnt + 1, 0);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
cnt = 1;
long jobv;
for (int i = 1, op, jobl, jobr; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
if (op == 1) {
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
in.nextToken();
jobv = (long) in.nval;
add(jobl, jobr, jobv, 1, n, 1);
} else {
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
out.println(query(jobl, jobr, 1, n, 1));
}
}
// 本题每组测试数据都单独运行
// 可以不写clear方法
// 但是如果多组测试数据串行调用
// 就需要加上清空逻辑
clear();
out.flush();
out.close();
br.close();
}
}
2276. 统计区间中的整数数目 - 力扣(LeetCode)
由于修改操作为特殊性:将一段区域所有的值都改为1,这个操作是一次性的,如果下一次还修改这段区域的值,相当于没有操作;而查询操作只查询所有范围,所以就不需要懒更新机制,只要维持好上层的sum值即可
java
// 开点线段树的实现
// 为了所有语言的同学都容易改出来
// 选择用静态空间的方式实现
// 该方法的打败比例不高但是非常好想
// 有兴趣的同学可以研究其他做法
class CountIntervals {
// 支持的最大范围
public static int n = 1000000000;
// 空间大小定成这个值是实验的结果
public static int LIMIT = 700001;
public static int[] left = new int[LIMIT];
public static int[] right = new int[LIMIT];
public static int[] sum = new int[LIMIT];
public static int cnt = 1;
public CountIntervals() {
Arrays.fill(left, 1, cnt + 1, 0);
Arrays.fill(right, 1, cnt + 1, 0);
Arrays.fill(sum, 1, cnt + 1, 0);
cnt = 1;
}
public static void up(int h, int l, int r) {
sum[h] = sum[l] + sum[r];
}
// 这个题的特殊性在于,只有改1的操作,没有改0的操作
// 理解这个就可以分析出不需要懒更新机制,原因有两个
// 1) 查询操作永远查的是整个范围1的数量,不会有小范围的查询,每次都返回sum[1]
// 这意味着只要能把sum[1]更新正确即可,up函数可以保证这一点
// 2) 一个范围已经全是1,那以后都会是1,没有必要把全是1的懒更新信息向下传递
// 这个函数的功能比线段树能做到的范围修改功能简单很多
// 功能有阉割就意味着存在优化的点
public static void setOne(int jobl, int jobr, int l, int r, int i) {
if (sum[i] == r - l + 1) {
return;
}
if (jobl <= l && r <= jobr) {
sum[i] = r - l + 1;
} else {
int mid = (l + r) >> 1;
if (jobl <= mid) {
if (left[i] == 0) {
left[i] = ++cnt;
}
setOne(jobl, jobr, l, mid, left[i]);
}
if (jobr > mid) {
if (right[i] == 0) {
right[i] = ++cnt;
}
setOne(jobl, jobr, mid + 1, r, right[i]);
}
up(i, left[i], right[i]);
}
}
public void add(int left, int right) {
setOne(left, right, 1, n, 1);
}
public int count() {
return sum[1];
}
}
1.设置每个范围的最大值、次大值、最大值个数数组。如果某个子节点的最大值和父节点的最大值不同,就为其"打上标签"。
- 更新区间最值的操作有三中情况:
如果v大于区域的最大值,就不需要修改
如果v介于最大值和次大值之间,就更新懒信息
如果v小于次大值,此时就不能用线段树的常见修改操作,需要先传递懒信息,然后暴力下发
- 只要节点上还有次大值,说明下面还有标签。
java
// 线段树的区间最值操作(hdu测试)
// 给定一个长度为n的数组arr,实现支持以下三种操作的结构
// 操作 0 l r x : 把arr[l..r]范围的每个数v,更新成min(v, x)
// 操作 1 l r : 查询arr[l..r]范围上的最大值
// 操作 2 l r : 查询arr[l..r]范围上的累加和
// 三种操作一共调用m次,做到时间复杂度O(n * log n + m * log n)
// 测试链接 : https://acm.hdu.edu.cn/showproblem.php?pid=5306
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code03_SegmentTreeSetminQueryMaxSum2 {
public static int MAXN = 1000001;
public static int LOWEST = -1;
public static long[] sum = new long[MAXN << 2];
public static int[] max = new int[MAXN << 2];
public static int[] cnt = new int[MAXN << 2];
public static int[] sem = new int[MAXN << 2];
public static void up(int i) {
int l = i << 1;
int r = i << 1 | 1;
sum[i] = sum[l] + sum[r];
max[i] = Math.max(max[l], max[r]);
if (max[l] > max[r]) {
cnt[i] = cnt[l];
sem[i] = Math.max(sem[l], max[r]);
} else if (max[l] < max[r]) {
cnt[i] = cnt[r];
sem[i] = Math.max(max[l], sem[r]);
} else {
cnt[i] = cnt[l] + cnt[r];
sem[i] = Math.max(sem[l], sem[r]);
}
}
public static void down(int i) {//懒更新的下发不会影响到孩子节点的次大值
lazy(i << 1, max[i]);
lazy(i << 1 | 1, max[i]);
}
// 一定是没有颠覆掉次大值的懒更新信息下发,也就是说:
// 最大值被压成v,并且v > 严格次大值的情况下
// sum和max怎么调整
public static void lazy(int i, int v) {
if (v < max[i]) {
sum[i] -= ((long) max[i] - v) * cnt[i];
max[i] = v;
}
}
public static void build(int l, int r, int i) throws IOException {
if (l == r) {
// 不能生成原始数组然后build
// 因为这道题空间非常极限
// 生成原始数组然后build
// 空间就是会超过限制
// 所以build的过程直接从输入流读入
// 一般情况下不会这么极限的
in.nextToken();
sum[i] = max[i] = (int) in.nval;
cnt[i] = 1;
sem[i] = LOWEST;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(i);
}
}
public static void setMin(int jobl, int jobr, int jobv, int l, int r, int i) {
if (jobv >= max[i]) {
return;
}
if (jobl <= l && r <= jobr && sem[i] < jobv) {
lazy(i, jobv);
} else {
down(i);
int mid = (l + r) >> 1;
if (jobl <= mid) {
setMin(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
setMin(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i);
}
}
public static int queryMax(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return max[i];
}
down(i);
int mid = (l + r) >> 1;
int ans = Integer.MIN_VALUE;
if (jobl <= mid) {
ans = Math.max(ans, queryMax(jobl, jobr, l, mid, i << 1));
}
if (jobr > mid) {
ans = Math.max(ans, queryMax(jobl, jobr, mid + 1, r, i << 1 | 1));
}
return ans;
}
public static long querySum(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return sum[i];
}
down(i);
int mid = (l + r) >> 1;
long ans = 0;
if (jobl <= mid) {
ans += querySum(jobl, jobr, l, mid, i << 1);
}
if (jobr > mid) {
ans += querySum(jobl, jobr, mid + 1, r, i << 1 | 1);
}
return ans;
}
// 为了不生成原始数组
// 让build函数可以直接从输入流拿数据
// 所以把输入输出流定义成全局静态变量
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StreamTokenizer in = new StreamTokenizer(br);
public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
in.nextToken();
int testCases = (int) in.nval;
for (int t = 1; t <= testCases; t++) {
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
build(1, n, 1);
for (int i = 1, op, jobl, jobr, jobv; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
if (op == 0) {
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
in.nextToken();
jobv = (int) in.nval;
setMin(jobl, jobr, jobv, 1, n, 1);
} else if (op == 1) {
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
out.println(queryMax(jobl, jobr, 1, n, 1));
} else {
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
out.println(querySum(jobl, jobr, 1, n, 1));
}
}
}
out.flush();
out.close();
br.close();
}
}
增加操作:如果一段区域增加某一值,他所产生的标签数量就是logn,所以增加的势能就是(logn)^2
java
// 线段树范围增加操作 + 区间最值操作
// 给定一个长度为n的数组arr,实现支持以下四种操作的结构
// 操作 0 l r x : 把arr[l..r]范围的每个数v,增加x
// 操作 1 l r x : 把arr[l..r]范围的每个数v,更新成min(v, x)
// 操作 2 l r : 查询arr[l..r]范围上的最大值
// 操作 3 l r : 查询arr[l..r]范围上的累加和
// 对数器验证
public class Code04_SegmentTreeAddSetminQueryMaxSum {
public static int MAXN = 500001;
public static long LOWEST = Long.MIN_VALUE;
// 原始数组
public static int[] arr = new int[MAXN];
// 累加和信息(查询信息)
public static long[] sum = new long[MAXN << 2];
// 最大值信息(只是查询信息,不再是懒更新信息,懒更新功能被maxAdd数组替代了)
public static long[] max = new long[MAXN << 2];
// 最大值个数(查询信息)
public static int[] cnt = new int[MAXN << 2];
// 严格次大值(查询信息)
public static long[] sem = new long[MAXN << 2];
// 最大值的增加幅度(懒更新信息)
public static long[] maxAdd = new long[MAXN << 2];
// 除最大值以外其他数字的增加幅度(懒更新信息)
public static long[] otherAdd = new long[MAXN << 2];
public static void up(int i) {
int l = i << 1;
int r = i << 1 | 1;
sum[i] = sum[l] + sum[r];
max[i] = Math.max(max[l], max[r]);
if (max[l] > max[r]) {
cnt[i] = cnt[l];
sem[i] = Math.max(sem[l], max[r]);
} else if (max[l] < max[r]) {
cnt[i] = cnt[r];
sem[i] = Math.max(max[l], sem[r]);
} else {
cnt[i] = cnt[l] + cnt[r];
sem[i] = Math.max(sem[l], sem[r]);
}
}
public static void lazy(int i, int n, long maxAddv, long otherAddv) {
sum[i] += maxAddv * cnt[i] + otherAddv * (n - cnt[i]);
max[i] += maxAddv;
sem[i] += sem[i] == LOWEST ? 0 : otherAddv;
maxAdd[i] += maxAddv;
otherAdd[i] += otherAddv;
}
public static void down(int i, int ln, int rn) {
int l = i << 1;
int r = i << 1 | 1;
// 为什么拿全局最大值不写成 : tmp = max[i]
// 因为父亲范围的最大值可能已经被懒更新任务修改过了
// 现在希望拿的是懒更新之前的最大值
// 子范围的max值没有修改过,所以写成 : tmp = Math.max(max[l], max[r])
long tmp = Math.max(max[l], max[r]);
if (max[l] == tmp) {
lazy(l, ln, maxAdd[i], otherAdd[i]);
} else {
lazy(l, ln, otherAdd[i], otherAdd[i]);
}
if (max[r] == tmp) {
lazy(r, rn, maxAdd[i], otherAdd[i]);
} else {
lazy(r, rn, otherAdd[i], otherAdd[i]);
}
maxAdd[i] = otherAdd[i] = 0;
}
public static void build(int l, int r, int i) {
if (l == r) {
sum[i] = max[i] = arr[l];
sem[i] = LOWEST;
cnt[i] = 1;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(i);
}
maxAdd[i] = otherAdd[i] = 0;
}
public static void add(int jobl, int jobr, long jobv, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
lazy(i, r - l + 1, jobv, jobv);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
add(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
add(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i);
}
}
public static void setMin(int jobl, int jobr, long jobv, int l, int r, int i) {
if (jobv >= max[i]) {
return;
}
if (jobl <= l && r <= jobr && sem[i] < jobv) {
lazy(i, r - l + 1, jobv - max[i], 0);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
setMin(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
setMin(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i);
}
}
public static long querySum(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return sum[i];
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
long ans = 0;
if (jobl <= mid) {
ans += querySum(jobl, jobr, l, mid, i << 1);
}
if (jobr > mid) {
ans += querySum(jobl, jobr, mid + 1, r, i << 1 | 1);
}
return ans;
}
}
public static long queryMax(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return max[i];
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
Long ans = Long.MIN_VALUE;
if (jobl <= mid) {
ans = Math.max(ans, queryMax(jobl, jobr, l, mid, i << 1));
}
if (jobr > mid) {
ans = Math.max(ans, queryMax(jobl, jobr, mid + 1, r, i << 1 | 1));
}
return ans;
}
}
public static void main(String[] args) {
System.out.println("测试开始");
int n = 2000;
int v = 5000;
int t = 1000000;
randomArray(n, v);
long[] check = new long[n + 1];
for (int i = 1; i <= n; i++) {
check[i] = arr[i];
}
build(1, n, 1);
for (int i = 1, op, a, b, jobl, jobr, jobv; i <= t; i++) {
op = (int) (Math.random() * 4);
a = (int) (Math.random() * n) + 1;
b = (int) (Math.random() * n) + 1;
jobl = Math.min(a, b);
jobr = Math.max(a, b);
if (op == 0) {
jobv = (int) (Math.random() * v * 2) - v;
add(jobl, jobr, jobv, 1, n, 1);
checkAdd(check, jobl, jobr, jobv);
} else if (op == 1) {
jobv = (int) (Math.random() * v * 2) - v;
setMin(jobl, jobr, jobv, 1, n, 1);
checkSetMin(check, jobl, jobr, jobv);
} else if (op == 2) {
long ans1 = queryMax(jobl, jobr, 1, n, 1);
long ans2 = checkQueryMax(check, jobl, jobr);
if (ans1 != ans2) {
System.out.println("出错了!");
}
} else {
long ans1 = querySum(jobl, jobr, 1, n, 1);
long ans2 = checkQuerySum(check, jobl, jobr);
if (ans1 != ans2) {
System.out.println("出错了!");
}
}
}
System.out.println("测试结束");
}
// 为了验证
public static void randomArray(int n, int v) {
for (int i = 1; i <= n; i++) {
arr[i] = (int) (Math.random() * v * 2) - v;
}
}
// 为了验证
public static void checkAdd(long[] check, int jobl, int jobr, long jobv) {
for (int i = jobl; i <= jobr; i++) {
check[i] += jobv;
}
}
// 为了验证
public static void checkSetMin(long[] check, int jobl, int jobr, long jobv) {
for (int i = jobl; i <= jobr; i++) {
check[i] = Math.min(check[i], jobv);
}
}
// 为了验证
public static long checkQueryMax(long[] check, int jobl, int jobr) {
long ans = Long.MIN_VALUE;
for (int i = jobl; i <= jobr; i++) {
ans = Math.max(ans, check[i]);
}
return ans;
}
// 为了验证
public static long checkQuerySum(long[] check, int jobl, int jobr) {
long ans = 0;
for (int i = jobl; i <= jobr; i++) {
ans += check[i];
}
return ans;
}
}
P6242 【模板】线段树 3(区间最值操作、区间历史最值) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.如果子区间的最大值和父区间的最大值相同,就把maxadd、otheradd、maxaddtop、otheraddtop一起向下传递
2.如果子区间的最大值和父区间的最大值不同,那么就只把other add、otheraddtop向下传递
java
// 区间最值和历史最值
// 给定两个长度都为n的数组A和B,一开始两个数组完全一样
// 任何操作做完,都更新B数组,B[i] = max(B[i],A[i])
// 实现以下五种操作,一共会调用m次
// 操作 1 l r v : A[l..r]范围上每个数加上v
// 操作 2 l r v : A[l..r]范围上每个数A[i]变成min(A[i],v)
// 操作 3 l r : 返回A[l..r]范围上的累加和
// 操作 4 l r : 返回A[l..r]范围上的最大值
// 操作 5 l r : 返回B[l..r]范围上的最大值
// 1 <= n、m <= 5 * 10^5
// 测试链接 : https://www.luogu.com.cn/problem/P6242
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static int MAXN = 500001;
public static long LOWEST = Long.MIN_VALUE;
public static int[] arr = new int[MAXN];
public static long[] sum = new long[MAXN << 2];
public static long[] max = new long[MAXN << 2];
public static int[] cnt = new int[MAXN << 2];
public static long[] sem = new long[MAXN << 2];
public static long[] maxAdd = new long[MAXN << 2];
public static long[] otherAdd = new long[MAXN << 2];
// 历史最大值
public static long[] maxHistory = new long[MAXN << 2];
// 最大值达到过的最大提升幅度(懒更新信息)
public static long[] maxAddTop = new long[MAXN << 2];
// 除最大值以外的其他数字,达到过的最大提升幅度(懒更新信息)
public static long[] otherAddTop = new long[MAXN << 2];
public static void up(int i) {
int l = i << 1;
int r = i << 1 | 1;
maxHistory[i] = Math.max(maxHistory[l], maxHistory[r]);
sum[i] = sum[l] + sum[r];
max[i] = Math.max(max[l], max[r]);
if (max[l] > max[r]) {
cnt[i] = cnt[l];
sem[i] = Math.max(sem[l], max[r]);
} else if (max[l] < max[r]) {
cnt[i] = cnt[r];
sem[i] = Math.max(max[l], sem[r]);
} else {
cnt[i] = cnt[l] + cnt[r];
sem[i] = Math.max(sem[l], sem[r]);
}
}
// maxAddv : 最大值增加多少
// otherAddv : 其他数增加多少
// maxUpv : 最大值达到过的最大提升幅度
// otherUpv : 其他数达到过的最大提升幅度
public static void lazy(int i, int n, long maxAddv, long otherAddv, long maxUpv, long otherUpv) {
maxHistory[i] = Math.max(maxHistory[i], max[i] + maxUpv);
maxAddTop[i] = Math.max(maxAddTop[i], maxAdd[i] + maxUpv);
otherAddTop[i] = Math.max(otherAddTop[i], otherAdd[i] + otherUpv);
sum[i] += maxAddv * cnt[i] + otherAddv * (n - cnt[i]);
max[i] += maxAddv;
sem[i] += sem[i] == LOWEST ? 0 : otherAddv;
maxAdd[i] += maxAddv;
otherAdd[i] += otherAddv;
}
public static void down(int i, int ln, int rn) {
int l = i << 1;
int r = i << 1 | 1;
long tmp = Math.max(max[l], max[r]);
if (max[l] == tmp) {
lazy(l, ln, maxAdd[i], otherAdd[i], maxAddTop[i], otherAddTop[i]);
} else {
lazy(l, ln, otherAdd[i], otherAdd[i], otherAddTop[i], otherAddTop[i]);
}
if (max[r] == tmp) {
lazy(r, rn, maxAdd[i], otherAdd[i], maxAddTop[i], otherAddTop[i]);
} else {
lazy(r, rn, otherAdd[i], otherAdd[i], otherAddTop[i], otherAddTop[i]);
}
maxAdd[i] = otherAdd[i] = maxAddTop[i] = otherAddTop[i] = 0;
}
public static void build(int l, int r, int i) {
if (l == r) {
sum[i] = max[i] = maxHistory[i] = arr[l];
sem[i] = LOWEST;
cnt[i] = 1;
} else {
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
up(i);
}
maxAdd[i] = otherAdd[i] = maxAddTop[i] = otherAddTop[i] = 0;
}
public static void add(int jobl, int jobr, long jobv, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
lazy(i, r - l + 1, jobv, jobv, jobv, jobv);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
add(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
add(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i);
}
}
public static void setMin(int jobl, int jobr, long jobv, int l, int r, int i) {
if (jobv >= max[i]) {
return;
}
if (jobl <= l && r <= jobr && sem[i] < jobv) {
lazy(i, r - l + 1, jobv - max[i], 0, jobv - max[i], 0);
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
if (jobl <= mid) {
setMin(jobl, jobr, jobv, l, mid, i << 1);
}
if (jobr > mid) {
setMin(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);
}
up(i);
}
}
public static long querySum(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return sum[i];
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
long ans = 0;
if (jobl <= mid) {
ans += querySum(jobl, jobr, l, mid, i << 1);
}
if (jobr > mid) {
ans += querySum(jobl, jobr, mid + 1, r, i << 1 | 1);
}
return ans;
}
}
public static long queryMax(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return max[i];
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
Long ans = Long.MIN_VALUE;
if (jobl <= mid) {
ans = Math.max(ans, queryMax(jobl, jobr, l, mid, i << 1));
}
if (jobr > mid) {
ans = Math.max(ans, queryMax(jobl, jobr, mid + 1, r, i << 1 | 1));
}
return ans;
}
}
public static long queryHistoryMax(int jobl, int jobr, int l, int r, int i) {
if (jobl <= l && r <= jobr) {
return maxHistory[i];
} else {
int mid = (l + r) >> 1;
down(i, mid - l + 1, r - mid);
Long ans = Long.MIN_VALUE;
if (jobl <= mid) {
ans = Math.max(ans, queryHistoryMax(jobl, jobr, l, mid, i << 1));
}
if (jobr > mid) {
ans = Math.max(ans, queryHistoryMax(jobl, jobr, mid + 1, r, i << 1 | 1));
}
return ans;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
build(1, n, 1);
long jobv;
for (int i = 1, op, jobl, jobr; i <= m; i++) {
in.nextToken();
op = (int) in.nval;
in.nextToken();
jobl = (int) in.nval;
in.nextToken();
jobr = (int) in.nval;
if (op == 1) {
in.nextToken();
jobv = (long) in.nval;
add(jobl, jobr, jobv, 1, n, 1);
} else if (op == 2) {
in.nextToken();
jobv = (long) in.nval;
setMin(jobl, jobr, jobv, 1, n, 1);
} else if (op == 3) {
out.println(querySum(jobl, jobr, 1, n, 1));
} else if (op == 4) {
out.println(queryMax(jobl, jobr, 1, n, 1));
} else {
out.println(queryHistoryMax(jobl, jobr, 1, n, 1));
}
}
out.flush();
out.close();
br.close();
}
}