Codeforces1556E Equilibrium (线段树 + 最大连续子段和 + 括号匹配 / RMQ) (好题)

题目链接: 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| aibi.

因此我们不妨设 c [ ] c[] c[], c i = b i − a i c_i = b_i - a_i ci=biai, 这样如果 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 1rci=0. 即: 加减操作次数应相同.
② 对于任意位置 p o s pos pos, 应满足 ∑ 1 p o s c i ≥ 0 \displaystyle \sum_1^{pos} c_i \ge 0 1posci0. 即: 任意的减操作之前应有足够的加操作.

我们发现上述信息实际上是对于 c c c的前缀和数组进行操作的, 因此不妨令 s [ ] s[] s[] c c c的前缀和数组.

那么上述条件可以转化为: ①: s [ r ] − s [ l − 1 ] = 0 s[r] - s[l - 1] = 0 s[r]s[l1]=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} si1即可. (代码仍用线段树实现了, 也可以用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;
}

END

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值