题解:P6864 [RC-03] 记忆
Problem
P6864
维护一个字符串 S,支持变成 (S)、变成 S() 以及撤销一次操作(保证该操作在被撤销前生效,可撤销撤销操作),求每次操作后的合法括号字串数量。
Solution
题解中大部分讲的都是矩阵
我们考虑将原字串每一对括号视为一个节点,每个节点的父亲是在原串上最小的能够包含节点代表的括号的括号对应的节点(即
这样,我们可以考虑树上 dp,我们记
其中前面的求和是子树内答案,后面的统计在这个括号内一层的括号的答案。
可以建一个虚拟根节点方便统计答案。
考虑操作 S(),相当于自己建一个新的节点,向虚拟根节点连边。
考虑操作 (S),可以将虚拟根节点当作这个节点,并新建一个新的虚拟根节点由该点连边。
考虑撤销操作,若该撤销使字符串少一对括号,相当于删除对应节点并将该节点所有子节点连到被删除节点处。若该撤销使字符串多一对括号(或者说恢复操作),相当于将从该店连到其父亲的子节点从其父亲中删去并连到该点上,再有该店连向其父亲。
为处理撤销操作,我们定义
当该节点删去时,有:
我们可以在每次修改后进行整条到根节点路径的更新。时间平均复杂度
Optimization
首先,所有节点可以先将
其次,对于一次删除或恢复,可以先用栈维护该节点到根节点的路径,取消该路径上的贡献,更改后在恢复贡献,这样这个算法的时间平均复杂度就降到
此外,我们发现,没有必要对每个节点维护
有了上面的优化后,转移就只有
这时我们发现,当一个节点未被删除时,它子树内的删数并不会影响到这个结点的祖先(因为这个的
Code
贴上我丑陋的代码(勿喷 qwq
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
ll x=0;
short f=1;
char c=getchar();
while(c>57||c<48){
if(c==45) f=-1;
else f=1;
c=getchar();
}
while(c<58&&c>47){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void write(ll x){
if(x<0ll) putchar(45),x=~x+1;
if(x>9ll) write(x/10);
putchar(x%10|48);
}
int Node[200005],Id=2;
int RT=1;
int alive[200005],g[200005],sg[200005],fa[200005];
ll ans=0;
int st[200005],tp;
signed main(){
alive[1]=alive[2]=ans=g[1]=sg[1]=fa[2]=g[2]=1;
int n=read();
for(int i=1;i<=n;i++){
int op=read();
if(op==1){
Node[i]=++Id;
fa[Id]=RT;
alive[Id]=1;
g[Id]=1;
sg[RT]++;
ans+=sg[RT];
}
if(op==2){
Node[i]=RT;
RT=++Id;
alive[RT]=1;
sg[RT]=1;
g[RT]=1;
ans++;
fa[Node[i]]=RT;
}
if(op==3){
int x=Node[i]=-Node[read()];
if(x<0){
x=-x;
//delete node x away
st[++tp]=x;
while(!alive[x=fa[x]])st[++tp]=x;
for(int i=tp;i;i--){
int o=st[i];
sg[fa[o]]-=g[o];
if(alive[fa[o]]){
ans-=1ll*sg[fa[o]]*g[o]+(1ll*g[o]*(g[o]+1)>>1);
}
else{
g[fa[o]]-=g[o];
}
}
x=-Node[i];
g[x]=sg[x];
ans-=sg[x]*(sg[x]+1)>>1;
alive[x]=0;
for(int i=1;i<=tp;i++){
int o=st[i];
if(alive[fa[o]]){
ans+=1ll*sg[fa[o]]*g[o]+(1ll*g[o]*(g[o]+1)>>1);
}
else{
g[fa[o]]+=g[o];
}
sg[fa[o]]+=g[o];
}
tp=0;
}
else{
//insert node x back
st[++tp]=x;
while(!alive[x=fa[x]])st[++tp]=x;
for(int i=tp;i;i--){
int o=st[i];
sg[fa[o]]-=g[o];
if(alive[fa[o]]){
ans-=1ll*sg[fa[o]]*g[o]+(1ll*g[o]*(g[o]+1)>>1);
}
else{
g[fa[o]]-=g[o];
}
}
x=Node[i];
alive[x]=1;
g[x]=1;
ans+=1ll*sg[x]*(sg[x]+1)>>1;
for(int i=1;i<=tp;i++){
int o=st[i];
if(alive[fa[o]]){
ans+=1ll*sg[fa[o]]*g[o]+(1ll*g[o]*(g[o]+1)>>1);
}
else{
g[fa[o]]+=g[o];
}
sg[fa[o]]+=g[o];
}
tp=0;
}
}
write(ans);putchar(10);
}
}
Extra
我在模拟赛赛时打完了如上优化,但还是能找到 Hack 数据,可按如下方法构造:
cout<<"200000\n";
for(int i=1;i<=66667;i++)cout<<"2\n";
for(int i=1;i<=66667;i++)cout<<"3 "<<66668-i<<"\n";
for(int i=133335;i<=200000;i++)cout<<"3 "<<i-1<<"\n";
但是,这个算法在随机情况下是