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

news2024/11/14 18:50:47

 1.修改:用到了相应的空间就开,没有用到就不开。cnt拓展节点编号,此时各范围的节点编号不再按照i*2和i*2+1的对应关系建立

2.查询:
如果查询时一段范围没有建立过,就说明这段范围的累加和就是0

3.空间估计:一次操作最差的情况就是从顶点节点沿两条边界线一直扎到末尾,所以每一次增加操作开辟2*logn空间,一共m次操作,所以大致开辟2*m*logn

 

P2781 传教 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

和静态实现线段树的区别就是找左右点的编号的方式不同

// 动态开点线段树
// 一共有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值即可


	// 开点线段树的实现
	// 为了所有语言的同学都容易改出来
	// 选择用静态空间的方式实现
	// 该方法的打败比例不高但是非常好想
	// 有兴趣的同学可以研究其他做法
	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.设置每个范围的最大值、次大值、最大值个数数组。如果某个子节点的最大值和父节点的最大值不同,就为其"打上标签"。

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

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

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

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

3. 只要节点上还有次大值,说明下面还有标签。

// 线段树的区间最值操作(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

// 线段树范围增加操作 + 区间最值操作
// 给定一个长度为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向下传递

 

// 区间最值和历史最值
// 给定两个长度都为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();
	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2094300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

尚品汇-项目目前存在问题、引入MQ(四十二)

目录&#xff1a; &#xff08;1&#xff09;目前存在的问题 &#xff08;2&#xff09;消息队列解决什么问题 &#xff08;3&#xff09;消息队列工具 RabbitMQ &#xff08;4&#xff09;搭建mq测试环境service-mq 下面我们先做的是前面后台管理系统商品上下架的没完成的…

C++逆向分析之条件语句和循环语句

一.C逆向条件结构基础入门 大家写过相关的算法吗&#xff1f; 加密代码中会涉及循环和分支&#xff0c;你要识别算法&#xff0c;首先就是需要将它的算法处理流程识别出来。当我们还原出等价的高级代码之后&#xff0c;就没有逆向分析人员的事情了&#xff0c;因为接下来涉及…

54. QButtonGroup的基本使用

1. 说明 在使用QT开发小软件时,使用最多的控件也许就是Button按钮了,一般情况下在界面上添加了一个Button,都会为这个Button添加一个相应的信号槽相应其点击事件。那么,如果在软件的其中一个界面添加了很多个Button,比如自定义的侧边菜单栏里可能会放置很多Button控件,如…

mac电脑里面的 磁盘分区,容器,宗卷,宗卷组的理解和使用

在mac电脑里面我们一般都是使用宗卷&#xff0c;他和我们常见的pc机器硬盘的分区是有区别的。 对于物理硬盘来说 不管是分区还是宗卷&#xff0c;他们都是逻辑上面的概念。 分区 mac电脑里面的分区 和 pc电脑中的分区差不多&#xff0c; 他们都是针对的物理硬盘&#xff0c;…

Linux用户层I2C读取LSM6DSL陀螺仪记录

硬件外设开发板Lubancat V2/dev/i2c-3LSM6DSL陀螺仪i2c(7bit地址0x6a) 开发板配置I2C 开发板采用Lubancat-V2&#xff0c;运行Linux内核4.19 使用I2C3外设 因为i2c3外设的设备树默认没有启用&#xff0c;所以在 /boot/uEnv/uEnv.txt 打开&#xff0c;也即取消i2c3-m0注释 随…

LINUX网络编程:应用层和协议定制

目录 1.协议定制 2.序列化和反序列化 ​编辑 3.tcp为什么是全双工 4.Tcp保证接收数据的完整性 1.协议定制 定制协议就是通信双方都遵守的协定 加上 符合通信和业务处理的结构化数据&#xff0c;就是struct或class。 例&#xff1a;佩奇使用微信向乔治发送了【你好】&…

51单片机——实时时钟

1、DS1302介绍 DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时&#xff0c;且具有闰年补偿等多种功能 RTC(Real Time Clock)&#xff1a;实时时钟&#xff0c;是一种集成电路&#xff0c;通常称为时钟…

华为OD机试真题 - 最长的顺子 - 动态规划(Java/Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试真题(Java/Python/JS/C/C++)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX…

【Linux修行路】进程通信——消息队列、信号量

目录 ⛳️推荐 一、消息队列 1.1 实现原理 1.2 消息队列接口 1.2.1 msgget——创建、获取一个消息队列 1.2.2 msgctl——释放消息队列、获取消息队列属性 1.2.3 msgsnd——发送数据 1.2.4 msgrcv——从消息队列中检索数据块 1.3 消息队列的指令操作 二、信号量 2.1 …

Java、python、php版 保险业务管理与数据分析系统 社会保险档案管理系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

700. 二叉搜索树中的搜索(迭代)

目录 一&#xff1a;题目&#xff1a; 三&#xff1a;结果 二&#xff1a;代码&#xff1a;: 一&#xff1a;题目&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的…

Ubuntu 超详细保姆级安装教程(每步都有截图)

文章目录 下载安装VMware检查网络适配器Ubuntu下载创建虚拟机启动虚拟机安装 VWware Tools安装 SSH 连接工具配置静态IP 下载安装VMware Desktop Hypervisor Solutions | VMware 官网下载速度慢的可以使用我百度网盘分享的链接来下载 百度网盘 | VMware 17.5.2 百度网盘没有…

vulnhub靶场-DC2

一、环境配置 1.下载地址&#xff1a;https://www.vulnhub.com/entry/dc-2,311/ 2.靶场配置&#xff1a;Nat模式 更改hosts文件&#xff0c;官网提示需要更改hosts文件才能访问到网站&#xff0c;否则访问不了 kali进入编辑模式vim&#xff0c;添加上自己的靶机ip地址保存即可…

Java进阶13讲__第八讲

集合&#xff1a;Collection&#xff0c;List&#xff0c;Set&#xff0c;Map 集合体系 集合结构 单列集合 1.Collection 1.初识Collection package cn.hdc.oop8.Collection;import java.util.ArrayList; import java.util.HashSet; import java.util.List;/*** 目标&#xf…

前端面试模拟:常见的3个JavaScript经典考题

在一次备受期待的前端开发高级岗位面试中&#xff0c;你紧张地走进了会议室&#xff0c;对面坐着的是一位经验丰富的技术面试官。窗外阳光明媚&#xff0c;屋内却有一丝令人紧张的静谧。 第一问&#xff1a;如何使用JavaScript实现事件委托&#xff1f; 面试官微微一笑&#xf…

实战项目:俄罗斯方块(一)

文章目录 &#x1f34a;自我介绍&#x1f34a;vt100 控制码1.概述2.数字格式①常用数字控制码②常用控制码 &#x1f34a;绘制方格 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我…

猫头虎分享:关于 Mac OS 系统 .DS_Store 文件的起源、作用及管理全指南

&#x1f42f;猫头虎分享&#xff1a;关于 Mac OS 系统 .DS_Store 文件的起源、作用及管理全指南 &#x1f42f; 猫头虎 分享&#xff1a;关于 Mac OS系统 .DS_Store 文件的起源和作用 今天猫头虎带您深入探讨 Mac OS 系统中的 .DS_Store 文件。作为一名开发者&#xff0c;您…

科研绘图系列:R语言差异基因四分图(Quad plot)

介绍 四分图(Quad plot)是一种数据可视化技术,通常用于展示四个变量之间的关系。它由四个子图组成,每个子图都显示两个变量之间的关系。四分图的布局通常是2x2的网格,每个格子代表一个变量对的散点图。 在四分图中,通常: 第一个子图显示变量A和B的关系。第二个子图显示…

足浴城消费系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;商品分类管理&#xff0c;商品信息管理&#xff0c;购买信息管理&#xff0c;会员卡申请管理&#xff0c;包厢信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告…

苍穹外卖项目前端DAY01

前端DAY01 1、基于脚手架创建前端工程 使用Vue CLI创建前端工程&#xff1a; 方式一&#xff1a;vue create 项目名称方式二&#xff1a;vue ui&#xff08;比较慢&#xff09; 2、vue基本使用方法 Vue的组件文件以.vue结尾&#xff0c;每个组件由三个部分组成&#xff1a; …