势能线段树

感觉这个科技挺有用,能解决一些常用而又阴间的操作,所以就写一下(

模板:给你一个长度为 \(n\) 序列,\(m\) 次操作,有三种操作:

0 l r x: 对于 \(i\in [l,r]\) 执行 \(a_i=\min (a_i, x)\)
1 l r:输出 \(\max(a_l,a_{l+1}...a_r)\)
2 l r:输出 \(\sum\limits^r_{i=l}a_i\)

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

其实这道题可以不用势能线段树,我一来想的是一个分块的做法:

取块长 \(\sqrt{n}\),对于每一块,我们维护原序列这个块内的元素排序后的结果。

修改时散块暴力重构,复杂度 \(O(\sqrt{n})\),修改整块时还是暴力重构,从大往小扫到 \(a_i\le t\) 为止,因为序列只会减小不会增加,这样复杂度 \(O(\sqrt{n})\)

总的复杂度 \(O(m\sqrt{n})\),稳过校内OJ过于老年估计跑不了

但这个玩意儿但凡我们区间修改加个 \(\max\) 操作它就 \(O(m\sqrt{n\log n})\) 了(整块要二分而不能暴力了),这就不好,所以我们使用势能线段树。

后两个操作是线段树的常规操作,而修改操作对区间和的影响不太好直接计算。

对于线段树每个节点,我们维护 \(mx,mxcnt,secmx,sum\) 分别表示最大值,最大值出现次数,次大值,区间和。

每次修改到当前节点,且修改区间完全包含当前节点管辖的区间,则分为三种情况:

  1. \(mx\le x\),直接不管跑路。
  2. \(secmx<x\),当前节点 \(sum -= mxcnt\times (mx-x)\),打个懒标记。
  3. \(x\le secmx\),递归进入左右子树继续修改。

那么到此时,我们就得到了一个至少正确性没有问题的算法。

但修改操作的第三种情况,不会拖累全局的复杂度吗?

考虑势能分析。设当前势能为这颗线段树中,每个节点管辖区间中不同的数字个数总和。开始的时候,势能为 \(O(n\log n)\)。并且修改操作显然最多让势能 \(+1\)

每当我们处于第三种情况,继续递归进入左右子树时,势能必定减少 \(1\)。而当势能 \(=1\) 时,不论哪个节点必定没有次大值,也就不会出现 \(x\le secmx\) 的情况。

即复杂度均摊下来是严格 \(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;
}
posted @ 2021-12-05 12:37  zqs2020  阅读(170)  评论(0编辑  收藏  举报