线段树统计序列问题(最大子段和/最长上升段/最长括号匹配)

最大子段和

Acwing 245 - 你能回答这些问题吗

题目链接

给定长度为 N N N的序列 A A A,以及 M M M条指令。每条指令可能为以下两种之一:

  1. 1   x   y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最大连续子段和。
  2. 2   x   y 2\ x\ y 2 x y,把 A [ x ] A[x] A[x]改成 y y y

对于每个查询指令,输出一个整数表示答案。

对于线段树的每一个节点,维护对应区间的最大前缀、最大后缀、区间内的元素和、区间最大子段和。

struct Tree {
	int l, r;
	LL sum, pre, suf, mx;
}t[2000050];

push_up函数:每次更新完两个子区间后,要通过子区间的信息维护当前信息的内容。

  1. 区间元素和sum:直接取两个区间和的和即可。
  2. 区间最大前缀:取左区间最大前缀、左区间元素和+右区间最大前缀中的最大值。
  3. 区间最大后缀:取右区间最大后缀、右区间元素和+左区间最大后缀中的最大值。
  4. 区间最大子段和:取左、右区间最大子段和、左区间最大后缀+右区间最小前缀三者中的最大值。
void pu(int ni) {
	t[ni].sum = t[ni << 1].sum + t[ni << 1 | 1].sum;
	t[ni].pre = max(t[ni << 1].pre, t[ni << 1].sum + t[ni << 1 | 1].pre);
	t[ni].suf = max(t[ni << 1 | 1].suf, t[ni << 1].suf + t[ni << 1 | 1].sum);
	t[ni].mx = max({t[ni << 1].mx, t[ni << 1 | 1].mx, t[ni << 1].suf + t[ni << 1 | 1].pre});
}

建树:
注意维护的四个值的初始化。

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].sum = t[ni].pre = t[ni].suf = t[ni].mx = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

更新操作:单点修改,所以没有懒标记,也不需要push_down函数。
单点修改后push_up即可。

void fix(int ni, int pos, int x) {
	if (t[ni].l == t[ni].r and t[ni].l == pos) {
		t[ni].sum = t[ni].pre = t[ni].suf = t[ni].mx = x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (pos <= mid) fix(ni << 1, pos, x);
	else fix(ni << 1 | 1, pos, x);
	pu(ni); 
}

查询操作:查询操作稍微复杂一些,因为涉及到查询区间和当前区间的相对关系。

  1. 如果查询区间包含了当前区间,那么返回当前区间的全部四个信息。(为了方便起见,直接返回线段树的节点。)
  2. 如果查询区间完全在当前区间的左半区间,则只查询左半区间并返回所得节点信息。
  3. 如果查询区间完全在当前区间的右半区间,则只查询右半区间并返回所得节点信息。
  4. 如果查询区间横跨了左右区间,那么分别查询左右两个区间。由于是不断递归获得的信息,所以虽然查询了左右两个区间,但是获得的信息只有我们查询区间的信息。使用与pu函数相同的思想对查询结果进行push_up,将push_up出来的结果进行返回。
Tree query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni];
	}
	LL mid = (t[ni].l + t[ni].r) >> 1;
	if (r <= mid) return query(ni << 1, l, r);
	else if (mid < l) return query(ni << 1 | 1, l, r);
	else {
		Tree res, t1, t2;
		t1 = query(ni << 1, l, r);
		t2 = query(ni << 1 | 1, l, r);
		res.sum = t1.sum + t2.sum;
		res.pre = max(t1.pre, t1.sum + t2.pre);
		res.suf = max(t2.suf, t1.suf + t2.sum);
		res.mx = max({t1.mx, t2.mx, t1.suf + t2.pre});
		return res;
	} 
}

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n, m;
int a[500050];

struct Tree {
	int l, r;
	LL sum, pre, suf, mx;
}t[2000050];

void pu(int ni) {
	t[ni].sum = t[ni << 1].sum + t[ni << 1 | 1].sum;
	t[ni].pre = max(t[ni << 1].pre, t[ni << 1].sum + t[ni << 1 | 1].pre);
	t[ni].suf = max(t[ni << 1 | 1].suf, t[ni << 1].suf + t[ni << 1 | 1].sum);
	t[ni].mx = max({t[ni << 1].mx, t[ni << 1 | 1].mx, t[ni << 1].suf + t[ni << 1 | 1].pre});
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].sum = t[ni].pre = t[ni].suf = t[ni].mx = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

void fix(int ni, int pos, int x) {
	if (t[ni].l == t[ni].r and t[ni].l == pos) {
		t[ni].sum = t[ni].pre = t[ni].suf = t[ni].mx = x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (pos <= mid) fix(ni << 1, pos, x);
	else fix(ni << 1 | 1, pos, x);
	pu(ni); 
}

Tree query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni];
	}
	LL mid = (t[ni].l + t[ni].r) >> 1;
	if (r <= mid) return query(ni << 1, l, r);
	else if (mid < l) return query(ni << 1 | 1, l, r);
	else {
		Tree res, t1, t2;
		t1 = query(ni << 1, l, r);
		t2 = query(ni << 1 | 1, l, r);
		res.sum = t1.sum + t2.sum;
		res.pre = max(t1.pre, t1.sum + t2.pre);
		res.suf = max(t2.suf, t1.suf + t2.sum);
		res.mx = max({t1.mx, t2.mx, t1.suf + t2.pre});
		return res;
	} 
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	build_tree(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		int op, x, y;
		cin >> op >> x >> y;
		if (op == 1) {
			if (x > y) swap(x, y);
			cout << query(1, x, y).mx << "\n";
		}
		else {
			fix(1, x, y);
		}
	}
	return 0;
}

最长上升段

CF1567E - Non-Decreasing Dilemma

题目链接

给定长度为 N N N的序列 A A A,以及 M M M条指令。每条指令可能为以下两种之一:

  1. 1   x   y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最长的不降序列长度。
  2. 2   x   y 2\ x\ y 2 x y,把 A [ x ] A[x] A[x]改成 y y y

对于线段树的每一个区间,维护最大前缀上升长度、最大后缀上升长度、区间元素个数。

struct Tree {
	int l, r;
	LL tot, pre, suf;
}t[2000050];

push_up函数

维护最大前缀和:如果左区间最后的值小于等于右区间第一个值,那么当前区间最大前缀上升长度为左区间元素个数+右区间最大前缀上升长度;否则为左区间最大前缀上升长度。

维护最大后缀和:如果左区间最后的值小于等于右区间第一个值,那么当前区间最大后缀上升长度为右区间元素个数+左区间最大后缀上升长度;否则为右区间最大后缀上升长度。

维护区间元素和:为左右区间的元素个数之和。

void pu(Tree &ret, Tree &L, Tree &R) {
	ret.l = L.l; ret.r = R.r;
	if (L.pre == (L.r - L.l + 1) and a[L.r] <= a[R.l]) {
		ret.pre = (L.r - L.l + 1) + R.pre;
	}
	else ret.pre = L.pre;
	if (R.suf == (R.r - R.l + 1) and a[L.r] <= a[R.l]) {
		ret.suf = (R.r - R.l + 1) + L.suf;
	}
	else ret.suf = R.suf;
	ret.tot = L.tot + R.tot;
	if (a[L.r] <= a[R.l]) {
		ret.tot += (L.suf * R.pre);
	}
}

void pu(int ni) {
	pu(t[ni], t[ni << 1], t[ni << 1 | 1]);
}

单点修改:只需要更新a[x]的值,然后push_up即可。

void fix(int ni, int pos, int x) {
	if (t[ni].l == t[ni].r and t[ni].l == pos) {
		a[pos] = x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (pos <= mid) fix(ni << 1, pos, x);
	else fix(ni << 1 | 1, pos, x);
	pu(ni);
}

区间查询:和最大子段和原理相同。

Tree query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni];
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (r <= mid) return query(ni << 1, l, r);
	else if (mid < l) return query(ni << 1 | 1, l, r);
	else {
		Tree t1 = query(ni << 1, l, r);
		Tree t2 = query(ni << 1 | 1, l, r);
		Tree res;
		pu(res, t1, t2);
		return res; 
	}
}

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n, q;
int a[200050];

struct Tree {
	int l, r;
	LL tot, pre, suf;
}t[2000050];

void pu(Tree &ret, Tree &L, Tree &R) {
	ret.l = L.l; ret.r = R.r;
	if (L.pre == (L.r - L.l + 1) and a[L.r] <= a[R.l]) {
		ret.pre = (L.r - L.l + 1) + R.pre;
	}
	else ret.pre = L.pre;
	if (R.suf == (R.r - R.l + 1) and a[L.r] <= a[R.l]) {
		ret.suf = (R.r - R.l + 1) + L.suf;
	}
	else ret.suf = R.suf;
	ret.tot = L.tot + R.tot;
	if (a[L.r] <= a[R.l]) {
		ret.tot += (L.suf * R.pre);
	}
}

void pu(int ni) {
	pu(t[ni], t[ni << 1], t[ni << 1 | 1]);
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].pre = t[ni].suf = t[ni].tot = 1;
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

void fix(int ni, int pos, int x) {
	if (t[ni].l == t[ni].r and t[ni].l == pos) {
		a[pos] = x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (pos <= mid) fix(ni << 1, pos, x);
	else fix(ni << 1 | 1, pos, x);
	pu(ni);
}

Tree query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni];
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (r <= mid) return query(ni << 1, l, r);
	else if (mid < l) return query(ni << 1 | 1, l, r);
	else {
		Tree t1 = query(ni << 1, l, r);
		Tree t2 = query(ni << 1 | 1, l, r);
		Tree res;
		pu(res, t1, t2);
		return res; 
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	build_tree(1, 1, n);
	for (int i = 1; i <= q; ++i) {
		int op, x, y;
		cin >> op >> x >> y;
		if (op == 1) {
			fix(1, x, y);
		}
		else {
			cout << query(1, x, y).tot << "\n";
		}
	}
	return 0;
}

最长括号匹配

给定长度为 N N N的括号序列 A A A,以及 M M M条指令。每条指令可能为以下两种之一:

  1. 1   x   y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最长匹配的括号序列。
  2. 2   x   y 2\ x\ y 2 x y,把 A [ x ] A[x] A[x]改成 y y y

如果维护可以匹配的括号维护起来很麻烦,所以我们反过来,维护不能匹配的括号。

线段树的区间内维护不能匹配的左括号数量、不能匹配的右括号数量。

struct Tree {
	int l, r, lcnt, rcnt;
}t[4000050];

push_up函数:

维护新区间的不能匹配的左括号数量、不能匹配的右括号数量。

可以发现,结合区间时,可以产生的新配对中,左括号出自左区间,右括号出自右区间。所以可以被匹配掉的左括号全部是左区间的,可以被匹配掉的全部时右区间的。可以匹配的数量是左区间左括号和右区间右括号的数量最小值。

新的区间的未匹配左括号数量是两个区间的左括号数量和-匹配掉的左括号。
新的区间的未匹配右括号数量是两个区间的右括号数量和-匹配掉的右括号。

void pu(Tree &ret, Tree &L, Tree &R) {
	ret.l = L.l; ret.r = R.r;
	int match = min(L.lcnt, R.rcnt);
	ret.lcnt = L.lcnt + R.lcnt - match;
	ret.rcnt = L.rcnt + R.rcnt - match;
}

void pu(int ni) {
	pu(t[ni], t[ni << 1], t[ni << 1 | 1]);
}

本题不涉及修改,单点查询与前两种题型思想相同,故不多赘述。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

string s;
int a[1000050];
int n, m;

struct Tree {
	int l, r, lcnt, rcnt;
}t[4000050];

void pu(Tree &ret, Tree &L, Tree &R) {
	ret.l = L.l; ret.r = R.r;
	int match = min(L.lcnt, R.rcnt);
	ret.lcnt = L.lcnt + R.lcnt - match;
	ret.rcnt = L.rcnt + R.rcnt - match;
}

void pu(int ni) {
	pu(t[ni], t[ni << 1], t[ni << 1 | 1]);
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		if (a[l] == 1) {
			t[ni].lcnt = 1; t[ni].rcnt = 0;
		}
		else {
			t[ni].rcnt = 1; t[ni].lcnt = 0;
		}
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

Tree query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni];
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (r <= mid) return query(ni << 1, l, r);
	else if (mid < l) return query(ni << 1 | 1, l, r);
	else {
		Tree t1 = query(ni << 1, l, r), t2 = query(ni << 1 | 1, l, r);
		Tree res;
		pu(res, t1, t2);
		return res;
	}
} 

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> s;
	n = s.length();
	for (int i = 0; i < n; ++i) {
		if (s[i] == '(') a[i + 1] = 1;
		else a[i + 1] = -1; 
	}
	build_tree(1, 1, n);
	cin >> m;
	for (int i = 1; i <= m; ++i) {
		int x, y;
		cin >> x >> y;
		Tree ans = query(1, x, y);
		cout << (y - x + 1) - ans.lcnt - ans.rcnt << "\n";
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值