拙者
题目描述
我们定义一个括号序列是高兴的,当且仅当它右括号的个数不超过左括号的个数。
现在你要维护一个长度为 $n$ 的括号序列,编号为 $1−n$ ,支持以下三种操作:
1 $p$ 表示将第 $p$ 个括号反向,既 $($ 变为 $)$ , $)$ 变为 $($ 。
2 $l$ $r$ 表示询问:如果要使第 $l$ 个括号到第 $r$ 个括号组成的序列的每个前缀都是高兴的,至少需要删去多少个位置上的括号。
3 $l$ $r$ 表示询问:如果要使第 $l$ 个括号到第 $r$ 个括号组成的序列的每个前缀和每个后缀都是高兴的,至少需要删去多少个位置上的括号。
数据范围
$n \le 2 \times 10^5,q \le 3 \times 10^5$
题解
考虑将 $[l,r]$ 的括号序列拉出,做个前缀和,然后表示成一个函数图象
如果是2操作的话,显然是 $max(-min,0)$
对于3操作,考虑贪心,将前缀先弄成合法,此时图象会发生偏移,每个点 $i$ 最终变为 $x_i-min(x_j
)(j<i)$
要使得后缀变得合法,则右端点要成为图象的最高点,因此要使前缀合法后的max降至右端点后才合法
因此用线段树维护极差即可
代码
#include <bits/stdc++.h> using namespace std; const int N=3e5+5; int n,a[N],q;char ch; struct T{int in,ax,s,df;}t[N<<2],V; #define Ls k<<1 #define Rs k<<1|1 #define mid ((l+r)>>1) T hb(T x,T y){ V.in=min(x.in,y.in+x.s); V.ax=max(x.ax,y.ax+x.s); V.df=max(max(x.df,y.df),y.ax+x.s-x.in); V.s=x.s+y.s; return V; } void build(int k,int l,int r){ if (l==r){ if (~a[l]) t[k]=(T){0,1,1,1}; else t[k]=(T){-1,0,-1,0}; return; } build(Ls,l,mid);build(Rs,mid+1,r); t[k]=hb(t[Ls],t[Rs]); } void upd(int k,int l,int r,int x){ if (l==r){ if (~a[l]) t[k]=(T){0,1,1,1}; else t[k]=(T){-1,0,-1,0}; return; } if (mid>=x) upd(Ls,l,mid,x); else upd(Rs,mid+1,r,x); t[k]=hb(t[Ls],t[Rs]); } T qry(int k,int l,int r,int L,int R){ if (L<=l && r<=R) return t[k]; if (mid>=R) return qry(Ls,l,mid,L,R); if (mid<L) return qry(Rs,mid+1,r,L,R); return hb(qry(Ls,l,mid,L,R),qry(Rs,mid+1,r,L,R)); } int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf(" %c",&ch), a[i]=((ch=='(')?1:-1); build(1,1,n);scanf("%d",&q); for (int op,x,y;q--;){ scanf("%d%d",&op,&x); if (op<2) a[x]=-a[x],upd(1,1,n,x); else if (op<3){ scanf("%d",&y); T v=qry(1,1,n,x,y); printf("%d\n",max(0,-v.in)); } else{ scanf("%d",&y); T v=qry(1,1,n,x,y); printf("%d\n",max(0,-v.in)+v.df-(v.s-v.in)); } } return 0; }