洛谷 P3215 - 括号修复
DS真tm要人命
洛谷题目页面传送门
给定一个括号串\(a,|a|=n\)。你需要支持以下\(4\)种\(q\)次操作:
- \(\texttt{Replace}\ l\ r\ x\):将\(a_{l\sim r}\)内的字符全部改为\(x\);
- \(\texttt{Swap}\ l\ r\):将\(a_{l\sim r}\)翻转;
- \(\texttt{Invert}\ l\ r\):将\(a_{l\sim r}\)内的所有字符\(\texttt(\to\texttt),\texttt)\to\texttt(\);
- \(\texttt{Query}\ l\ r\):查询\(a_{l\sim r}\)至少要改变多少个字符才能变成合法括号串。保证一定能在有限次改动内变成合法括号串。
\(n,q\in\left[1,10^5\right]\)。
先来考虑怎么算一个括号串\(s\)至少要改变多少个字符才能变成合法括号串。由于这是一个复杂的数据结构题,所以我们坚信结论比较简单
显然,\(s\)能在有限次改动内变成合法括号串当且仅当\(2\mid |s|\)。以下默认\(2\mid |s|\)。
将\(\texttt(\)看成\(1\),\(\texttt)\)看成\(-1\)是解决括号串合法问题的惯用套路。设\(bal(x)=\begin{cases}1&x=\texttt(\\-1&x=\texttt)\end{cases}\),\(Bal_{s,i}=\sum\limits_{j=1}^ibal(s_j)\),则括号串\(s\)合法当且仅当\(\forall i\in[1,|s|],Bal_{s,i}\geq0\)且\(Bal_{s,|s|}=0\)。
考虑算出\(Bal_s\)数组。先假设只需要\(\forall i\in[1,|s|],Bal_{s,i}\geq0\)实现。对于每个\(i\)使得\(Bal_{s,i}<0\),我们要将\(s_i\)左边若干个\(\texttt)\)改成\(\texttt(\)使得\(Bal_{s,i}\geq0\)。显然,改一次会令\(Bal_{s,i}=Bal_{s,i}+2\),那么最少需要将\(s_i\)左边\(\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil\)个\(\texttt)\)改成\(\texttt(\)。又因为如果\(\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil<\max\limits_{j=1}^{i-1}\left\{\left\lceil\dfrac{-Bal_{s,j}}2\right\rceil\right\}\)的话,那么前面必有至少\(\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil\)次改动,已满足要求;否则显然可以找到办法只改前面的\(\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil\)个括号使得前面的所有\(Bal\)值都非负。所以若只需要\(\forall i\in[1,|s|],Bal_{s,i}\geq0\),那么最少改动数为\(\max\limits_{i=1}^{|s|}\left\{\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil\right\}\)。
此时已经\(\forall i\in[1,|s|],Bal_{s,i}\geq0\)了。再加上\(Bal_{s,|s|}=0\)的条件。显然此时\(Bal_{s,|s|}\geq0\),我们要考虑将若干\(\texttt(\)改成\(\texttt)\)使得\(Bal_{s,|s|}=0\)。显然,改一次会令\(Bal_{s,|s|}=Bal_{s,|s|}-2\),那么最少需要将\(\dfrac{Bal_{s,|s|}}2\)个\(\texttt(\)改成\(\texttt)\)。又因为先前\(\texttt)\to\texttt(\)的那些改动是保障\(\forall i\in[1,|s|],Bal_{s,i}\geq0\)的基础,肯定动不得,所以只能另外挑选\(\dfrac{Bal_{s,|s|}}2\)个\(\texttt(\)改成\(\texttt)\)。于是得出结论:括号串\(s\)至少要改变\(\max\limits_{i=1}^{|s|}\left\{\left\lceil\dfrac{-Bal_{s,i}}2\right\rceil\right\}+\dfrac{Bal_{s,|s|}+2\max\limits_{i=1}^{|s|}\left\{\left\lceil\frac{-Bal_{s,i}}2\right\rceil\right\}}2=\left\lceil\dfrac{-\min\limits_{i=1}^{|s|}\{Bal_{s,i}\}}2\right\rceil+\dfrac{Bal_{s,|s|}+2\left\lceil\frac{-\min\limits_{i=1}^{|s|}\{Bal_{s,i}\}}2\right\rceil}2\)个字符才能变成合法括号串。
所以我们只需要维护\(\min\limits_{i=1}^{r-l+1}\{Bal_{a_{l\sim r},i}\}\)和\(Bal_{a_{l\sim r},r-l+1}\)这两个值即可。考虑到有区间翻转操作,我们用平衡树维护,这里使用fhq-Treap。
设当前节点\(i\)表示\(a_x\),子树表示区间\([l,r]\),那么我们需要存储\(v_i=bal(a_x),bal\_all_i=Bal_{a_{l\sim r},r-l+1},Mn\_bal_i=\min\limits_{j=1}^{r-l+1}\{Bal_{a_{l\sim r},j}\}\)。为了在区间反转操作中\(\mathrm O(1)\)打懒标记,我们还需要存储\(mn\_baL_i\)。为了在区间取反操作中\(\mathrm O(1)\)打懒标记,我们还需要存储\(Mx\_bal_i,mx\_baL_i\)。此时上传时很容易达到\(\mathrm O(1)\)。此外还需要存储\(3\)种操作的懒标记。
由于有\(3\)种懒标记,我们需要强行规定下传时的顺序。不妨规定顺序为\(\texttt{Replace},\texttt{Swap},\texttt{Invert}\)。此时打懒标记时除了应该做的常规操作以外,打\(\texttt{Replace}\)的懒标记时要将另\(2\)个懒标记清空。这里还有一个注意的地方:下传懒标记时有一个原则,就是参数不能含有非懒标记的其他存储的值,因为它们只能表现此节点的现状,并不能表现它经历了什么。我之前直接将\(\texttt{Replace}\)的懒标记定义为bool
,然后下传时的参数为\(v_i\),这样由于\(\texttt{Invert}\)的存在会出错,导致我盯着电脑看&自闭了一晚上。所以要定义为int
当作下传时的参数,\(\texttt{Invert}\)时只改\(v_i\)不改\(\texttt{Replace}\)的懒标记。
这还是一个比较模板的平衡树题吧……
(这个毒瘤卡常题还需要开O3优化才能过……)
下面是AC代码:
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f;
mt19937 rng(20060617/*信仰优化*/);
const int N=100000;
int n/*括号串长度*/,qu/*操作数*/;
char a[N+5];//括号串
struct fhq_treap{//fhq-Treap
int sz/*点数*/,root/*根*/;
struct node{unsigned key;int lson,rson,sz,v,bal_all,Mn_bal,mn_baL,Mx_bal,mx_baL,lz_chg/*Replace懒标记*/;bool lz_rev/*Swap懒标记*/,lz_inv/*Invert懒标记*/;}nd[N+1];
#define key(p) nd[p].key
#define lson(p) nd[p].lson
#define rson(p) nd[p].rson
#define sz(p) nd[p].sz
#define v(p) nd[p].v
#define bal_all(p) nd[p].bal_all
#define Mn_bal(p) nd[p].Mn_bal
#define mn_baL(p) nd[p].mn_baL
#define Mx_bal(p) nd[p].Mx_bal
#define mx_baL(p) nd[p].mx_baL
#define lz_chg(p) nd[p].lz_chg
#define lz_rev(p) nd[p].lz_rev
#define lz_inv(p) nd[p].lz_inv
void sprup(int p){//上传
sz(p)=sz(lson(p))+1+sz(rson(p));
bal_all(p)=bal_all(lson(p))+v(p)+bal_all(rson(p));
Mn_bal(p)=min(Mn_bal(lson(p)),bal_all(lson(p))+v(p)+min(0,Mn_bal(rson(p))));
mn_baL(p)=min(mn_baL(rson(p)),bal_all(rson(p))+v(p)+min(0,mn_baL(lson(p))));
Mx_bal(p)=max(Mx_bal(lson(p)),bal_all(lson(p))+v(p)+max(0,Mx_bal(rson(p))));
mx_baL(p)=max(mx_baL(rson(p)),bal_all(rson(p))+v(p)+max(0,mx_baL(lson(p))));
}
int nwnd(int v){return nd[++sz]=node({rng(),0,0,1,v,v,v,v,v,v,0,false,false}),sz;}//新建节点
int bld(int l=1,int r=n){//建树
int mid=l+r>>1,p=nwnd(a[mid]=='('?1:-1);
if(l<=mid-1)lson(p)=bld(l,mid-1);
if(mid+1<=r)rson(p)=bld(mid+1,r);
return sprup(p),p;
}
void init(){//fhq-Treap初始化
nd[sz=0]=node({0,0,0,0,0,0,inf,inf,-inf,-inf,0,0,0});
root=bld();
}
void sprdwn_chg(int p,int v){//打Replace懒标记
v(p)=lz_chg(p)=v;
bal_all(p)=v*sz(p);
if(~v)Mn_bal(p)=mn_baL(p)=1,Mx_bal(p)=mx_baL(p)=sz(p);
else Mx_bal(p)=mx_baL(p)=-1,Mn_bal(p)=mn_baL(p)=-sz(p);
lz_rev(p)=lz_inv(p)=false;
}
void sprdwn_rev(int p){//打Swap懒标记
swap(lson(p),rson(p));
swap(Mn_bal(p),mn_baL(p));
swap(Mx_bal(p),mx_baL(p));
lz_rev(p)^=1;
}
void sprdwn_inv(int p){//打Invert懒标记
v(p)=-v(p);
bal_all(p)=-bal_all(p);
swap(Mn_bal(p),Mx_bal(p));Mn_bal(p)=-Mn_bal(p);Mx_bal(p)=-Mx_bal(p);
swap(mn_baL(p),mx_baL(p));mn_baL(p)=-mn_baL(p);mx_baL(p)=-mx_baL(p);
lz_inv(p)^=1;
}
void sprdwn(int p){//下传
if(lz_chg(p)){
if(lson(p))sprdwn_chg(lson(p),lz_chg(p));
if(rson(p))sprdwn_chg(rson(p),lz_chg(p));
lz_chg(p)=0;
}
if(lz_rev(p)){
if(lson(p))sprdwn_rev(lson(p));
if(rson(p))sprdwn_rev(rson(p));
lz_rev(p)=false;
}
if(lz_inv(p)){
if(lson(p))sprdwn_inv(lson(p));
if(rson(p))sprdwn_inv(rson(p));
lz_inv(p)=false;
}
}
pair<int,int> split(int x,int p=-1){~p||(p=root);
if(!x)return mp(0,p);
pair<int,int> sp;
sprdwn(p);
if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
return sp=split(x-sz(lson(p))-1,rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
}
int mrg(int p,int q){
if(!p||!q)return p|q;
sprdwn(p);sprdwn(q);
if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
return lson(q)=mrg(p,lson(q)),sprup(q),q;
}
void chg(int l,int r,char v){//区间赋值
pair<int,int> sp=split(l-1),sp0=split(r-l+1,sp.Y);
sprdwn_chg(sp0.X,v=='('?1:-1);
root=mrg(mrg(sp.X,sp0.X),sp0.Y);
}
void rev(int l,int r){//区间翻转
pair<int,int> sp=split(l-1),sp0=split(r-l+1,sp.Y);
sprdwn_rev(sp0.X);
root=mrg(mrg(sp.X,sp0.X),sp0.Y);
}
void inv(int l,int r){//区间取反
pair<int,int> sp=split(l-1),sp0=split(r-l+1,sp.Y);
sprdwn_inv(sp0.X);
root=mrg(mrg(sp.X,sp0.X),sp0.Y);
}
int least(int l,int r){//查询操作
pair<int,int> sp=split(l-1),sp0=split(r-l+1,sp.Y);
int tmp1=max(0,(-Mn_bal(sp0.X)+1)/2),tmp2=bal_all(sp0.X);
// cout<<Mn_bal(sp0.X)<<" "<<bal_all(sp0.X)<<"\n";
return root=mrg(mrg(sp.X,sp0.X),sp0.Y),tmp1+(tmp2+2*tmp1)/2;
}
void dfs(int p=-1)/*调试用*/{~p||(p=root);
if(!p)return;
sprdwn(p);
dfs(lson(p));
// printf("node#%d:lson=%d rson=%d v=%d all=%d Mn=%d mn=%d Mx=%d mx=%d\n",p,lson(p),rson(p),v(p),bal_all(p),Mn_bal(p),mn_baL(p),Mx_bal(p),mx_baL(p));
// putchar(~v(p)?'(':')');
dfs(rson(p));
}
}trp;
int main(){
cin>>n>>qu>>a+1;
trp.init();//fhq-Treap初始化
while(qu--){
string tp;int x,y;char z;
cin>>tp>>x>>y;
if(tp=="Replace")cin>>z,trp.chg(x,y,z);
else if(tp=="Swap")trp.rev(x,y);
else if(tp=="Invert")trp.inv(x,y);
else cout<<trp.least(x,y)<<"\n";
// trp.dfs();//puts("");
}
return 0;
}