开点线段树、区间最值和历史最值

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];
		}
	}

问题 - 5306 (hdu.edu.cn)

1.设置每个范围的最大值、次大值、最大值个数数组。如果某个子节点的最大值和父节点的最大值不同,就为其"打上标签"。

  1. 更新区间最值的操作有三中情况:

如果v大于区域的最大值,就不需要修改

如果v介于最大值和次大值之间,就更新懒信息

如果v小于次大值,此时就不能用线段树的常见修改操作,需要先传递懒信息,然后暴力下发

  1. 只要节点上还有次大值,说明下面还有标签。
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();
	}

}
相关推荐
算法歌者22 分钟前
[算法]入门1.矩阵转置
算法
林开落L37 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色37 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download39 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
SoraLuna1 小时前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎1 小时前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
YSRM1 小时前
Experimental Analysis of Dedicated GPU in Virtual Framework using vGPU 论文分析
算法·gpu算力·vgpu·pci直通
韭菜盖饭2 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode