题目链接: Compressed Bracket Sequence
2021.9.8 进行了博客翻新 Codeforces1263E Editor 由本题学习我们可以得出结论, 下文的方法一维护最大连续子段和, 等价于维护最大前缀和, 因此方法一与方法二等价. 只不过在线段树中, 我们不是必须维护前缀和, 而是可以同1263E的方式维护原序列信息.
大致题意
给出长度为n的序列 a , b a, b a,b.
对于每个询问区间 [ l , r ] [l, r] [l,r]: 你可以进行若干次操作, 每次操作必须选择偶数个下标 p 1 , p 2 , p 3 , p 4 . . . p_1, p_2, p_3, p_4... p1,p2,p3,p4... 对于第奇数个下标, 在 a p i a_{p_i} api处+1, 对于第偶数个下标, 在 b p i b_{p_i} bpi处+1.
问: 能否在进行若干次操作后, 使得 a i = b i , i ∈ [ l , r ] a_i = b_i, i \in [l, r] ai=bi,i∈[l,r]. 如果可以, 输出最小操作次数, 否则输出-1. (每次询问都是对原始序列进行询问)
注: 奇数个, 偶数个下标: 即在 a p 1 , a p 3 . . . a_{p_1}, a_{p_3}... ap1,ap3...处+1, b p 2 , b p 4 . . . b_{p_2}, b_{p_4}... bp2,bp4...处+1.
解题思路
本题给出两种解法, 供大家参考.
思维
在b数组加, 我们也可以看作是在a数组减. 因此我们把题目操作转化为对a数组加减. 简称为 加操作 和 减操作.
我们对于询问区间 [ l , r ] [l, r] [l,r], 对于位置 i i i, 如果 a i < b i a_i < b_i ai<bi, 则我们需要执行加操作, 如果 a i > b i a_i > b_i ai>bi, 则需要执行减操作. 需要执行的次数为: ∣ a i − b i ∣ |a_i - b_i| ∣ai−bi∣.
因此我们不妨设 c [ ] c[] c[], c i = b i − a i c_i = b_i - a_i ci=bi−ai, 这样如果 c i > 0 c_i > 0 ci>0, 表示执行 c i c_i ci次加操作, 否则表示执行 − c i -c_i −ci次减操作.
解法一:
首先我们考虑是否可以使得序列平衡:
我们考虑减操作执行前, 我们需要执行加操作. 如果我们把加操作看作左括号, 减操作看作右括号, 这样我们得到了一个括号序列.
则序列可以平衡 <==> 括号序列可以完全匹配.
接下来我们考虑如果序列可以平衡, 那么最小的操作次数是多少?
对于每个加操作而言, 一定优先与离自己最近的减操作匹配是最优的. 我们把加操作看为正, 减操作看为负, 那么最小的操作次数就是 [ l , r ] [l, r] [l,r]的最大连续子段和.
到此, 我们把本题拆分成了 括号序列 + 最大连续子段和的做法. 用线段树维护即可.
解法二:
对于询问区间 [ l , r ] [l, r] [l,r], 我们不妨先去除左端点 l l l的限制, 讨论 [ 1 , r ] [1, r] [1,r]的情况:
若区间有解应满足:
①
∑
1
r
c
i
=
0
\displaystyle \sum_1^r c_i = 0
1∑rci=0. 即: 加减操作次数应相同.
② 对于任意位置
p
o
s
pos
pos, 应满足
∑
1
p
o
s
c
i
≥
0
\displaystyle \sum_1^{pos} c_i \ge 0
1∑posci≥0. 即: 任意的减操作之前应有足够的加操作.
我们发现上述信息实际上是对于 c c c的前缀和数组进行操作的, 因此不妨令 s [ ] s[] s[]为 c c c的前缀和数组.
那么上述条件可以转化为: ①: s [ r ] − s [ l − 1 ] = 0 s[r] - s[l - 1] = 0 s[r]−s[l−1]=0 ②: m i n ( { s i } ) ≥ 0 , i ∈ [ 1 , r ] min(\{s_i\}) \ge 0, i\in[1, r] min({si})≥0,i∈[1,r], 即需要维护 s i s_i si的最小值.
接下来再看有解情况下的最小操作次数:
不难发现, 同解法一的思路, 其实最小操作次数为: m a x ( { s i } ) , i ∈ [ 1 , r ] max(\{s_i\}), i\in[1, r] max({si}),i∈[1,r]. 即需要维护 s i s_i si的最大值.
此时我们就把 [ 1 , r ] [1, r] [1,r]区间的情况, 分解成了维护 s s s的最大值与最小值问题.
考虑到原询问是 [ l , r ] [l, r] [l,r], 相较于 [ 1 , r ] [1, r] [1,r]区间的最值, 只需要减去 s i − 1 s_{i - 1} si−1即可. (代码仍用线段树实现了, 也可以用st表查询)
AC代码
解法一:
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
int a[N];
struct node {
int l, r;
ll sum, fmax, lmax, rmax; //维护最大连续子段和
ll vl, vr; //维护括号序列
}t[N << 2];
void pushup(node& p, node& l, node& r) {
p.sum = l.sum + r.sum;
p.lmax = max(l.lmax, l.sum + r.lmax);
p.rmax = max(r.rmax, r.sum + l.rmax);
p.fmax = max({ l.fmax, r.fmax, l.rmax + r.lmax });
ll qaq = min(l.vl, r.vr);
p.vl = l.vl + r.vl - qaq, p.vr = l.vr + r.vr - qaq;
}
void pushup(int x) { pushup(t[x], t[x << 1], t[x << 1 | 1]); }
void build(int l, int r, int x = 1) {
t[x] = { l, r, a[l], a[l], a[l], a[l], 0, 0 };
if (l == r) {
a[l] > 0 ? t[x].vl = a[l] : t[x].vr = -a[l];
return;
}
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
pushup(x);
}
node ask(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x];
int mid = t[x].l + t[x].r >> 1;
if (r <= mid) return ask(l, r, x << 1);
if (l > mid) return ask(l, r, x << 1 | 1);
node res = { 0, 0, 0, 0, 0, 0, 0, 0 };
node left = ask(l, r, x << 1), right = ask(l, r, x << 1 | 1);
pushup(res, left, right);
return res;
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) scanf("%d", &a[i]);
rep(i, n) {
int b; scanf("%d", &b);
a[i] = b - a[i];
}
build(1, n);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
node qaq = ask(l, r);
if (qaq.vl or qaq.vr) puts("-1");
else printf("%lld\n", qaq.fmax);
}
return 0;
}
解法二:
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
ll a[N];
struct node {
int l, r;
ll fmax, fmin;
}t[N << 2];
void pushup(node& p, node& l, node& r) {
p.fmax = max(l.fmax, r.fmax);
p.fmin = min(l.fmin, r.fmin);
}
void pushup(int x) { pushup(t[x], t[x << 1], t[x << 1 | 1]); }
void build(int l, int r, int x = 1) {
t[x] = { l, r, a[l], a[l] };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
pushup(x);
}
node ask(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x];
int mid = t[x].l + t[x].r >> 1;
if (r <= mid) return ask(l, r, x << 1);
if (l > mid) return ask(l, r, x << 1 | 1);
node res = { 0, 0, 0, 0 };
node left = ask(l, r, x << 1), right = ask(l, r, x << 1 | 1);
pushup(res, left, right);
return res;
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) scanf("%lld", &a[i]);
rep(i, n) {
int b; scanf("%d", &b);
a[i] = b - a[i];
}
rep(i, n) a[i] += a[i - 1];
build(1, n);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
node qaq = ask(l, r);
ll sum = a[r] - a[l - 1];
ll fmin = qaq.fmin - a[l - 1];
if (sum or fmin < 0) puts("-1");
else printf("%lld\n", qaq.fmax - a[l - 1]);
}
return 0;
}