Potential energy line segment tree

Posted by youwh on Sun, 05 Dec 2021 07:56:01 +0100

I think this technology is very useful and can solve some common and shady operations, so I write it(

Template: give you a sequence with a length of \ (n \) and \ (m \) operations. There are three operations:

0 l r x: execute \ (a_i=\min (a_i, x) \) for \ (i\in [l,r] \).
1 l r: output \ (\ max(a_l,a_{l+1}...a_r) \).
2 l r: output \ (\ sum \ limits ^ R {I = l} a _i \).

\(1\le n,m\le 2\times 10^5\).

In fact, there is no need to use the potential energy line segment tree for this problem. I think of a block method:

Take the block length \ (\ sqrt{n} \), and for each block, we maintain the sorted results of the elements in the block of the original sequence.

When modifying a block, the complexity is \ (O(\sqrt{n}) \), and when modifying the whole block, it is still violent reconstruction, sweeping from large to small to \ (a_i\le t \), because the sequence will only decrease and will not increase, so the complexity is \ (O(\sqrt{n}) \).

The overall complexity \ (O(m\sqrt{n}) \) is stable. OJ in school is too old to run.

But for this thing, if we add a \ (\ max \) operation to the interval modification, it will be \ (O(m\sqrt{n\log n}) \) (the whole block needs to be divided into two instead of violence), which is not good, so we use the potential energy line segment tree.

The latter two operations are the routine operations of the segment tree, and the influence of the modification operation on the interval sum is not easy to calculate directly.

For each node of the segment tree, we maintain \ (mx,mxcnt,secmx,sum \) to represent the maximum value, the number of occurrences of the maximum value, the second largest value, the interval and the sum respectively.

Each time you modify to the current node, and the modified interval completely includes the interval under the jurisdiction of the current node, there are three cases:

  1. \(mx\le x \), directly regardless of running.
  2. \(secmx < x \), the current node \ (sum -= mxcnt\times (mx-x) \), marked as lazy.
  3. \(x\le secmx \), recursively enter the left and right subtrees to continue modification.

So at this point, we get an algorithm that is at least correct.

But will the third case of modification not drag down the global complexity?

Consider potential energy analysis. Let the current potential energy be the sum of different numbers in the jurisdiction of each node in this segment tree. At the beginning, the potential energy is \ (O(n\log n) \). And the modification operation obviously makes the potential energy \ (+ 1 \) at most.

Whenever we are in the third case and continue to recursively enter the left and right subtrees, the potential energy must decrease \ (1 \). When the potential energy \ (= 1 \), no matter which node must have no second largest value, there will be no \ (x\le secmx \).

That is, the complexity is strictly \ (O(n\log n+m\log n) \).

#include <cstdio>
#define int long long

inline int max(const int x, const int y) {return x > y ? x : y;}
struct Node {
	int l, r, mx, mxcnt, semx, sum, lazy;
} tree[800005];
int a[200005];

inline void pushup(int O) {
	int v[] = {tree[O << 1].mx, tree[O << 1].semx, tree[O << 1 | 1].mx, tree[O << 1 | 1].semx};
	int c[] = {tree[O << 1].mxcnt, 0, tree[O << 1 | 1].mxcnt, 0};
	tree[O].mx = max(v[0], v[2]);
	tree[O].mxcnt = 0;
	if (v[0] == tree[O].mx) tree[O].mxcnt += c[0], v[0] = 0;
	if (v[2] == tree[O].mx) tree[O].mxcnt += c[2], v[2] = 0;
	tree[O].semx = max(v[0], max(v[1], max(v[2], v[3])));
	tree[O].sum = tree[O << 1].sum + tree[O << 1 | 1].sum;
}

inline void pushdown(int O) {
	if (tree[O].lazy < tree[O << 1].mx) {
		tree[O << 1].sum -= tree[O << 1].mxcnt * (tree[O << 1].mx - tree[O].lazy);
		tree[O << 1].mx = tree[O].lazy;
		tree[O << 1].lazy = tree[O].lazy;
	}
	if (tree[O].lazy < tree[O << 1 | 1].mx) {
		tree[O << 1 | 1].sum -= tree[O << 1 | 1].mxcnt * (tree[O << 1 | 1].mx - tree[O].lazy);
		tree[O << 1 | 1].mx = tree[O].lazy;
		tree[O << 1 | 1].lazy = tree[O].lazy;
	}
	tree[O].lazy = 1e18;
}
void build(int O, int L, int R) {
	tree[O].l = L, tree[O].r = R, tree[O].lazy = 1e18;
	if (L != R) {
		build(O << 1, L, L + R >> 1);
		build(O << 1 | 1, (L + R >> 1) + 1, R);
		pushup(O);
	} else tree[O].mx = tree[O].sum = a[L], tree[O].mxcnt = 1;
}
void update(int O, int L, int R, int x) {
	if (L <= tree[O].l && tree[O].r <= R) {
		if (tree[O].mx <= x) return;
		if (tree[O].semx < x) {
			tree[O].sum -= tree[O].mxcnt * (tree[O].mx - x), tree[O].mx = tree[O].lazy = x;
			return;
		}
		pushdown(O);
		update(O << 1, L, R, x);
		update(O << 1 | 1, L, R, x);
		pushup(O);
		return;
	}
	int mid = tree[O].l + tree[O].r >> 1;
	pushdown(O);
	if (L <= mid) update(O << 1, L, R, x);
	if (mid < R) update(O << 1 | 1, L, R, x);
	pushup(O);
}
int getmax(int O, int L, int R) {
	if (L <= tree[O].l && tree[O].r <= R) return tree[O].mx;
	pushdown(O);
	int mid = tree[O].l + tree[O].r >> 1, ans = 0;
	if (L <= mid) ans = getmax(O << 1, L, R);
	if (mid < R) ans = max(ans, getmax(O << 1 | 1, L, R));
	return ans;
}
int getsum(int O, int L, int R) {
	if (L <= tree[O].l && tree[O].r <= R) return tree[O].sum;
	pushdown(O);
	int mid = tree[O].l + tree[O].r >> 1, ans = 0;
	if (L <= mid) ans = getsum(O << 1, L, R);
	if (mid < R) ans += getsum(O << 1 | 1, L, R);
	return ans;
}

signed main() {
	int n, m;
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++ i) scanf("%lld", a + i);
	build(1, 1, n);
	while (m --) {
		int opt, l, r, v;
		scanf("%lld%lld%lld", &opt, &l, &r);
		if (opt == 0) {scanf("%lld", &v); if (v) update(1, l, r, v);}
		else if (opt == 1) printf("%lld\n", getmax(1, l, r));
		else printf("%lld\n", getsum(1, l, r));
	}
	return 0;
}