[蓝桥杯 2021 国 AB] 翻转括号序列
题目描述
给定一个长度为 的括号序列,要求支持两种操作:
-
将 区间内(序列中的第 个字符到第 个字符)的括号全部翻转(左括号变成右括号,右括号变成左括号)。
-
求出以 为左端点时,最长的合法括号序列对应的 (即找出最大的 使 是一个合法括号序列)。
输入格式
输入的第一行包含两个整数 ,分别表示括号序列长度和操作次数。
第二行包含给定的括号序列,括号序列中只包含左括号和右括号。
接下来 行,每行描述一个操作。如果该行为 1 L R, 表示第一种操作,区间为 ;如果该行为 2 L 表示第二种操作,左端点为 。
对于所有评测用例, 。
Solution
分析一下操作二
看到这两个操作不难想到这个问题大概可以用线段树解决,不难想到我们可以把左括号看成1,右括号看成-1。这个时候的合法序列就应该是
那如果用线段树直接去维护区间和,然后每次去查询右侧是否有这样一个点是很困难的,所以要把它转化成前缀和形式
所以对于每一个我们要找的是右侧最后一个符合上述式子的位置即可。
对于操作一的实现
而对于翻转区间操作,如果翻转的区间的,我们可以知道就是直接把全都取相反数而对于的区间应该全都减去某一个数
所以当时,的区间应该全都减去原来的,由于是区间操作,我们需要一个懒惰标记,记为。
像这种区间翻转一个很套路的做法就是
在线段树上翻转区间我们只要维护区间最大值和区间最小值,每次翻转就是交换最大最小值并且取负数,由于是区间操作,我们需要一个懒惰标记,记为。
//假设p是我们当前操作区间的节点id
tmp1 = mx[p], tmp2 = mn[p];
mx[p] = (~tmp2) + 1;//取反加1就是取相反数
mn[p] = (~tmp1) + 1;
lazy_rev[p] ^= 1;
对于操作二的实现
很显然右端点所在的位置是可以二分的,那我们先考虑直接进行二分,我们每次去二分一个位置然后验证区间的最小值是否小于,小于我们就去左侧区间,否则就去右侧区间,但是我们要注意如果右侧区间最小值大于其实也是没有答案的,因为我们要找的右端点
这个做法时间复杂度是,像我写法不太好就可能被卡掉,要常数非常小才有可能通过。所以我们得考虑把这个二分的过程搬到线段树上去,线段树的很多操作本质上就是在做二分,我们可以利用这个二分的过程。
基于朴素二分的优化
先看一下我一开始错误的二分方式
其实有很显然的错误
在(2)处,虽然我们保证了当前的但是(线段树的区间的)依旧可能在的左侧,这就导致了mn[p << 1] < val这个语句会出错,我们要精确地找到这个右侧的最小值通过这一个是不太可行的(至少我好像实现不了)。我们可以发现如果的右侧有一个最小值小于那就一定是第一个小于的左侧点(因为数组具有连续性且这个点是第一个小于val的点)。所以我们直接去二分右侧第一个小于的点即可
int query_l(int p, int l, int r, int pos, int val) {
if (l == r) return l;
push_down(p);
int mid = l + r >> 1;
int ans = 0;
if (mn[p << 1] < val && pos <= mid) ans = query_l(p << 1, l, mid, pos, val);//pos在左侧区间并且最小值小于val
if (ans) return ans;//如果已经有值直接返回就行,因为我们要找的是第一个
if (mn[p << 1 | 1] < val) ans = query_l(p << 1 | 1, mid + 1, r, pos, val);//右侧要有<val的点才行
return ans;
}
但是我们发现还有问题,比如这样一个序列((())),他的数组就全都也就是不存在小于的点,此时我们就得再去二分一次,去找到右侧最后一个等于的点
int query_r(int p, int l, int r, int pos, int val) {
if (l == r) return l;
push_down(p);
int mid = l + r >> 1;
int ans = 0;
if (mn[p << 1 | 1] <= val) ans = query_r(p << 1 | 1, mid + 1, r, pos, val);//由于前一次二分我们已经保证了pos右侧所有pre全都>=val所以有小于等于就是等于。还是贪心的先去右边
if (ans) return ans;
if (mn[p << 1] <= val && pos <= mid) ans = query_r(p << 1, l, mid, pos, val);
return ans;
}
然后还要在注意一下处是的情况。
LAZY标记!!!
最后,还有最关键的一点也是线段树区间修改最需要注意的地方也就是这种多个标记的相互影响,打上标记不会对产生影响, 但是打上会对产生影响,由于是区间取反,所以要把也取反。