最大子段和
Acwing 245 - 你能回答这些问题吗
给定长度为 N N N的序列 A A A,以及 M M M条指令。每条指令可能为以下两种之一:
- 1 x y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最大连续子段和。
- 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函数:每次更新完两个子区间后,要通过子区间的信息维护当前信息的内容。
- 区间元素和sum:直接取两个区间和的和即可。
- 区间最大前缀:取左区间最大前缀、左区间元素和+右区间最大前缀中的最大值。
- 区间最大后缀:取右区间最大后缀、右区间元素和+左区间最大后缀中的最大值。
- 区间最大子段和:取左、右区间最大子段和、左区间最大后缀+右区间最小前缀三者中的最大值。
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);
}
查询操作:查询操作稍微复杂一些,因为涉及到查询区间和当前区间的相对关系。
- 如果查询区间包含了当前区间,那么返回当前区间的全部四个信息。(为了方便起见,直接返回线段树的节点。)
- 如果查询区间完全在当前区间的左半区间,则只查询左半区间并返回所得节点信息。
- 如果查询区间完全在当前区间的右半区间,则只查询右半区间并返回所得节点信息。
- 如果查询区间横跨了左右区间,那么分别查询左右两个区间。由于是不断递归获得的信息,所以虽然查询了左右两个区间,但是获得的信息只有我们查询区间的信息。使用与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 x y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最长的不降序列长度。
- 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 x y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最长匹配的括号序列。
- 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";
}
}