Codeforces Round #603 (Div. 2), problem: (E) Editor

题目大意

给你一连串的操作,L, R分别代表移动光标操作,其他字符表示在当前光标位置插入一个字符。如果该位置有字符,那么进行替换。让你判断当前输出的序列是否合法,如果合法,那么括号的最大嵌套深度是多少?

思路

首先这个序列的长度不会超过 1 0 6 10^6 106的长度,对于(我们可以看成在该位置加1,对于)我们可以看成在该位置减一,然后我们对该序列求前缀和,因为需要保证输出的括号序列合法,那么需要判断前缀和中是否有小于0的数以及整个序列的前缀和是否等于0,如果有,该序列不合法。除此之外。除此之外我们还需要维护前缀和的最大值,因为最大值对应着括号的最大嵌套深度。
于是,这就是个线段树的 区间更新的题目,线段树的每个点维护前缀和。对于每次更改根据需要更新[当前光标,n]里面所有前缀和的最大值和最小值。

代码1

#include <bits/stdc++.h>
using namespace std;
#define mid (L+R)/2
#define lson o<<1, L, mid
#define rson o<<1|1, mid+1, R
const int maxn = 1e6+100;
 
struct SegmentTree{
    int pmin[maxn<<2], pmax[maxn<<2], lazy[maxn<<2];
 
    void pushdown(int o){
        if(lazy[o]){
            lazy[o<<1] += lazy[o], lazy[o<<1|1] += lazy[o];
            pmax[o<<1] += lazy[o], pmin[o<<1] += lazy[o];
            pmax[o<<1|1] += lazy[o], pmin[o<<1|1] += lazy[o];
            lazy[o] = 0;
        }
    }
 
    void pushup(int o){
        pmax[o] = max(pmax[o<<1], pmax[o<<1|1]);
        pmin[o] = min(pmin[o<<1], pmin[o<<1|1]);
    }
 
    void update(int o, int L, int R, int l, int r, int val){
        if(L >= l && R <= r){
            lazy[o] += val, pmax[o] += val, pmin[o] += val;
            return;
        }
        pushdown(o);
        if(l <= mid) update(lson, l, r, val);
        if(r > mid) update(rson, l, r, val);
        pushup(o);
    }
}tree;
 
char s[maxn], now[maxn];
int ans[maxn];
 
int main(){
    int n; scanf("%d %s", &n, s);
    int cursor = 1, sum = 0;
    for(int i = 0; i < n; i++){
        if(s[i] == 'R') cursor++;
        else if(s[i] == 'L') cursor = max(cursor-1, 1);
        else{
            if(s[i] == '('){
                if(now[cursor] == ')') sum += 2, tree.update(1, 1, n, cursor, n , 2);
                else if(now[cursor] == '(');
                else sum++, tree.update(1, 1, n, cursor, n, 1);
            }
            else if(s[i] == ')'){
                if(now[cursor] == ')') ;
                else if(now[cursor] == '(') sum -= 2, tree.update(1, 1, n, cursor, n, -2);
                else sum--, tree.update(1, 1, n, cursor, n, -1);
            }
            else{
                if(now[cursor] == ')') sum++, tree.update(1, 1, n, cursor, n, 1);
                else if(now[cursor] == '(') sum--, tree.update(1, 1, n, cursor, n, -1);
            }
            now[cursor] = s[i];
        }
        if(sum == 0 &&  tree.pmin[1] >= 0) ans[i] = tree.pmax[1];
        else ans[i] = -1;
    }
    for(int i = 0; i < n; i++) printf("%d ", ans[i]);
    return 0;
}

supplement

上一部分的代码即臃肿有复杂。还有更简便的方法。我们发现可以将区间更新转化为单点更新。每个点的sum值表示当前区间和,而不是前缀和。pmin和pmax数组也表示当前区间的前缀和的最小值和最大值,而不是从1到当前位置的前缀和。
那么如何进行区间并的操作和呢?具体操作如下:

pmin[o] = min(pmin[o<<1], psum[o<<1] + pmin[o<<1|1]);
pmax[o] = max(pmax[o<<1], psum[o<<1] + pmax[o<<1|1]);
psum[o] = psum[o<<1] + psum[o<<1|1];

因为不用考虑那么多情况直接处理到叶子结点,所以整个代码被压缩的很短。
代码如下:

#include <bits/stdc++.h>
using namespace std;
#define mid (L+R)/2
#define lson o<<1, L, mid
#define rson o<<1|1, mid+1, R
const int maxn = 1e6+100;
 
struct SegmentTree{
    int psum[maxn<<2], pmin[maxn<<2], pmax[maxn<<2];
 
    void pushup(int o){
        pmin[o] = min(pmin[o<<1], psum[o<<1] + pmin[o<<1|1]);
        pmax[o] = max(pmax[o<<1], psum[o<<1] + pmax[o<<1|1]);
        psum[o] = psum[o<<1] + psum[o<<1|1];
    }
 
    void update(int o, int L, int R, int p, char val){
        if(L == R){
            if(val == '(') psum[o] = pmin[o] = pmax[o] = 1;
            else if(val == ')') psum[o] = pmin[o] = -1, pmax[o] = 0;
            else psum[o] = pmin[o] = pmax[o] = 0;
            return ;
        }
        if(p <= mid) update(lson, p, val);
        else update(rson, p, val);
        pushup(o);
    }
}tree;
 
char s[maxn];
int ans[maxn];
 
int main(){
    int n; scanf("%d %s", &n, s);
    int cursor = 1;
    for(int i = 0; i < n; i++){
        if(s[i] == 'R') cursor++;
        else if(s[i] == 'L') cursor = max(1, cursor-1);
        else tree.update(1, 1, n, cursor, s[i]);
        if(tree.psum[1] == 0 && tree.pmin[1] >= 0) ans[i] = tree.pmax[1];
        else ans[i] = -1;
    }
    for(int i = 0; i < n; i++) printf("%d ", ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值