[DS记录] 线段树 Beats

0. 前言

所谓线段树 Beats,就是吉老师打出来的线段树。

1. 基本操作

P6242 线段树 3

  1. 区间加
  2. 区间取 min
  3. 区间最大值
  4. 区间和
  5. 区间历史最大值

首先考虑 1 3 4 咋做。就是直接做对吧。

然后 5 咋做,我们发现难处在于历史最大值的维护。(废话)

比如我现在 \(+= k\),再 \(-= v\)。(都是正数)如果这两个操作执行完下传, 我们发现第一个操作执行完的历史最大值。因为标记把他们合并成了 \(k - v\)

我们见招拆招,维护一个历史最大标记怎么样。就是 pushdown,我们先修改这个标记,再处理普通标记。

记这两个标记为 \(add_1, add_3\)。(为了适应后面的定义)

那么 \(add_1\) 为普通区间加标记。\(add_3\) 为下放 \(add_1\) 之前,\(add_1\) 的出现过的最大值。(标记的历史最大)


然后 2. 取 min 其实很简单。相信大家都知道这个东西需要均摊分析。先说明处理过程。

维护最大值 \(maxa\),严格次大值 \(se\),最大值出现次数 \(cnt\)。递归时,假设取 min 的是 \(val\)

  • 如果 \(val > maxa\),那么结束。
  • 如果 \(maxa > val > se\),那么可以通过 \(cnt\) 计算贡献。
  • 否则暴力递归。

复杂度为什么是对的?设一个节点的容(我的理解:一个抽象的东西,描述节点的信息量多少?)为区间中数的种类数。由于暴力递归必然使得 最大值 和 严格次大值 合并。容 -= 1。而所有节点的种类数之和是 \(O(n\log n)\)(n 个数两两不同,每个数对 \(log\) 个节点贡献 1)


怎么结合这两个玩意呢?我们考虑维护两套标记。

\(add_1, add_3\)\(add_2, add_4\) 前者针对最大值,后者针对非最大值。

然后 pushdown 也需要修改。如果左边最大值 = 区间最大值,那么左边最大值继承区间最大值的标记,否则继承非最大值的标记。而左边非最大值继承区间非最大值标记。右边同理。

点击查看代码
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
typedef long long ll;
typedef double db;
#define ep emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define fout freopen("out.out","w",stdout);
#define fin freopen("in.in","r",stdin);
#define DE {cerr << "QAQ" << endl;}
#define dd(x) cerr << #x" = " << x << endl;
inline int read() {
	int x=0, v=1,ch=getchar();
	while('0'>ch||ch>'9') {
		if(ch=='-') v=0;
		ch=getchar();
	}while('0'<=ch&&ch<='9') {
		x=(x*10)+(ch^'0');
		ch=getchar();
	}return v?x:-x;
}
inline char readc() {
	char ch = 0;
	while(ch < 'A' || ch > 'Z') ch = getchar();
	return ch; 
} 
int n;
const int MAX = 1e5 + 5;
struct node {
	int maxa, maxb;
	int add, hadd; // ¼Ó·¨±ê¼Ç£¬¼Ó·¨±ê¼ÇµÄÀúÊ·×î´ó 
	int cov, hcov; // ¸³Öµ±ê¼Ç£¬¸³Öµ±ê¼ÇµÄÀúÊ·×î´ó 
	int fl; 	   // ÊÇ·ñ½øÐÐÁ˸³Öµ±ê¼Ç 
	void upd_add(int k1, int k2) {
		// ËùνµÄ¸³Öµ ºÍ ¼Ó·¨»¥Ïàת»¯¡£ 
		// ¸Ð¾õÉÏ£º½øÐÐ cover ǰ£¬¹±Ï×¶¼ÔÚ¸³Öµ²Ù×÷ͳ¼Æ¡£ 
		maxb = max(maxb, maxa + k2);
		maxa += k1;
		if(fl) {
			hcov = max(hcov, cov + k2);
			cov += k1;
		}else {
			hadd = max(hadd, add + k2);
			add += k1; 
		}
	} 
	void upd_cov(int k1, int k2) {
		fl = 1;
		maxb = max(maxb, k2);
		maxa = cov = k1;
		hcov = max(hcov, k2);
	}
} tr[MAX << 2];
#define ls x << 1, l, mid
#define rs x << 1 | 1, mid + 1, r 
#define rt 1, 1, n
void ps(int x) {
	tr[x].maxa = max(tr[x << 1].maxa, tr[x << 1 | 1].maxa);
	tr[x].maxb = max(tr[x << 1].maxb, tr[x << 1 | 1].maxb);
}
void pd(int x) {
	if(tr[x].add || tr[x].hadd) {
		tr[x << 1].upd_add(tr[x].add, tr[x].hadd);
		tr[x << 1 | 1].upd_add(tr[x].add, tr[x].hadd);
		tr[x].add = tr[x].hadd = 0;
	}
	if(tr[x].fl) {
		tr[x << 1].upd_cov(tr[x].cov, tr[x].hcov);
		tr[x << 1 | 1].upd_cov(tr[x].cov, tr[x].hcov);
		tr[x].fl = 0;
	}
}
void add(int x, int l, int r, int s, int t, int v) {
	if(s <= l && r <= t) { tr[x].upd_add(v, max(v, 0)); return ; }
	int mid = l + r >> 1; pd(x);
	if(s <= mid) add(ls, s, t, v);
	if(mid < t) add(rs, s, t, v);
	ps(x);
}
void mdf(int x, int l, int r, int s, int t, int v) {
	if(s <= l && r <= t) { tr[x].upd_cov(v, v); return ; }
	int mid = l + r >> 1; pd(x);
	if(s <= mid) mdf(ls, s, t, v);
	if(mid < t) mdf(rs, s, t, v);
	ps(x);
}

int hans, ans;
void qry(int x, int l, int r, int s, int t) {
	if(s <= l && r <= t) {
		ans = max(ans, tr[x].maxa);
		hans = max(hans, tr[x].maxb);
		return ;
	} int mid = l + r >> 1; pd(x);
	if(s <= mid) qry(ls, s, t);
	if(mid < t) qry(rs, s, t);
	ps(x); 
} 

void build(int x, int l, int r) {
	if(l == r) {
		int v = read();
		tr[x].maxa = tr[x].maxb = v;
		return ;
	} 
	int mid = l + r >> 1;
	build(ls), build(rs), ps(x);
}

signed main() {
//	fin;
	n = read();
	build(rt);
	int T = read();
	while(T--) {
		char op = readc();
		int l = read(), r = read();
		if(op == 'Q') {
			ans = - 1 << 31;
			qry(rt, l, r);
			printf("%d\n", ans);
		}else if(op == 'A'){
			hans = - 1 << 31;
			qry(rt, l, r);
			printf("%d\n", hans);
		}else if(op == 'P') {
			int c = read();
			add(rt, l, r, c);
		}else {
			int c = read();
			mdf(rt, l, r, c);
		}
	}
	return 0;
}

CPU 监控

区间加,区间赋值,区间最大值,区间历史最大值。

多了个区间赋值。意味着我们需要理清楚两个标记之间的关系!(就像线段树 2)

实际上我们只有一个标记,\((add, cov)\),表示先区间加 \(add\),再区间赋值成 \(cov\)

因为如果我们进行了一次区间赋值,我们的区间加操作都可以变成区间赋值操作。

我们维护标记 \(add, hadd, cov, hcov, flag\) 表示:

区间加标记,区间加标记的历史最大,区间赋值标记,区间赋值标记的历史最大。以及上一次下传清空后之前有没有赋值标记。

  1. 区间加的下方 / 修改时,考虑上一次下传清空后之前有没有赋值标记。
  • 如果有:直接把区间加转化成区间赋值
  • 没有:区间加还是区间加(与上一个题一模一样)
  1. 区间赋值的下方 / 修改时,需要修改 flag
点击查看代码
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
typedef long long ll;
typedef double db;
#define ep emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define fout freopen("out.out","w",stdout);
#define fin freopen("in.in","r",stdin);
#define DE {cerr << "QAQ" << endl;}
#define dd(x) cerr << #x" = " << x << endl;
inline int read() {
	int x=0, v=1,ch=getchar();
	while('0'>ch||ch>'9') {
		if(ch=='-') v=0;
		ch=getchar();
	}while('0'<=ch&&ch<='9') {
		x=(x*10)+(ch^'0');
		ch=getchar();
	}return v?x:-x;
}
inline char readc() {
	char ch = 0;
	while(ch < 'A' || ch > 'Z') ch = getchar();
	return ch; 
} 
int n;
const int MAX = 1e5 + 5;
struct node {
	int maxa, maxb;
	int add, hadd; // ¼Ó·¨±ê¼Ç£¬¼Ó·¨±ê¼ÇµÄÀúÊ·×î´ó 
	int cov, hcov; // ¸³Öµ±ê¼Ç£¬¸³Öµ±ê¼ÇµÄÀúÊ·×î´ó 
	int fl; 	   // ÊÇ·ñ½øÐÐÁ˸³Öµ±ê¼Ç 
	void upd_add(int k1, int k2) {
		// ËùνµÄ¸³Öµ ºÍ ¼Ó·¨»¥Ïàת»¯¡£ 
		// ¸Ð¾õÉÏ£º½øÐÐ cover ǰ£¬¹±Ï×¶¼ÔÚ¸³Öµ²Ù×÷ͳ¼Æ¡£ 
		maxb = max(maxb, maxa + k2);
		maxa += k1;
		if(fl) {
			hcov = max(hcov, cov + k2);
			cov += k1;
		}else {
			hadd = max(hadd, add + k2);
			add += k1; 
		}
	} 
	void upd_cov(int k1, int k2) {
		fl = 1;
		maxb = max(maxb, k2);
		maxa = cov = k1;
		hcov = max(hcov, k2);
	}
} tr[MAX << 2];
#define ls x << 1, l, mid
#define rs x << 1 | 1, mid + 1, r 
#define rt 1, 1, n
void ps(int x) {
	tr[x].maxa = max(tr[x << 1].maxa, tr[x << 1 | 1].maxa);
	tr[x].maxb = max(tr[x << 1].maxb, tr[x << 1 | 1].maxb);
}
void pd(int x) {
	if(tr[x].add || tr[x].hadd) {
		tr[x << 1].upd_add(tr[x].add, tr[x].hadd);
		tr[x << 1 | 1].upd_add(tr[x].add, tr[x].hadd);
		tr[x].add = tr[x].hadd = 0;
	}
	if(tr[x].fl) {
		tr[x << 1].upd_cov(tr[x].cov, tr[x].hcov);
		tr[x << 1 | 1].upd_cov(tr[x].cov, tr[x].hcov);
		tr[x].fl = 0;
	}
}
void add(int x, int l, int r, int s, int t, int v) {
	if(s <= l && r <= t) { tr[x].upd_add(v, max(v, 0)); return ; }
	int mid = l + r >> 1; pd(x);
	if(s <= mid) add(ls, s, t, v);
	if(mid < t) add(rs, s, t, v);
	ps(x);
}
void mdf(int x, int l, int r, int s, int t, int v) {
	if(s <= l && r <= t) { tr[x].upd_cov(v, v); return ; }
	int mid = l + r >> 1; pd(x);
	if(s <= mid) mdf(ls, s, t, v);
	if(mid < t) mdf(rs, s, t, v);
	ps(x);
}

int hans, ans;
void qry(int x, int l, int r, int s, int t) {
	if(s <= l && r <= t) {
		ans = max(ans, tr[x].maxa);
		hans = max(hans, tr[x].maxb);
		return ;
	} int mid = l + r >> 1; pd(x);
	if(s <= mid) qry(ls, s, t);
	if(mid < t) qry(rs, s, t);
	ps(x); 
} 

void build(int x, int l, int r) {
	if(l == r) {
		int v = read();
		tr[x].maxa = tr[x].maxb = v;
		return ;
	} 
	int mid = l + r >> 1;
	build(ls), build(rs), ps(x);
}

signed main() {
//	fin;
	n = read();
	build(rt);
	int T = read();
	while(T--) {
		char op = readc();
		int l = read(), r = read();
		if(op == 'Q') {
			ans = - 1 << 31;
			qry(rt, l, r);
			printf("%d\n", ans);
		}else if(op == 'A'){
			hans = - 1 << 31;
			qry(rt, l, r);
			printf("%d\n", hans);
		}else if(op == 'P') {
			int c = read();
			add(rt, l, r, c);
		}else {
			int c = read();
			mdf(rt, l, r, c);
		}
	}
	return 0;
}
posted @ 2023-03-20 22:49  Lates  阅读(65)  评论(0)    收藏  举报