专栏文章
题解:P6864 [RC-03] 记忆
P6864题解参与者 15已保存评论 17
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 17 条
- 当前快照
- 1 份
- 快照标识符
- @miosmu5n
- 此快照首次捕获于
- 2025/12/03 00:29 3 个月前
- 此快照最后确认于
- 2025/12/03 00:29 3 个月前
Problem
Solution
题解中大部分讲的都是矩阵 的做法。但是我在模拟赛中乱搞得到一种更快的做法。
我们考虑将原字串每一对括号视为一个节点,每个节点的父亲是在原串上最小的能够包含节点代表的括号的括号对应的节点(即 是 的父亲节点,当前仅当 对应的括号包含 对应的括号,且不存在任何括号被 包含且包含 ),形成一棵树。
这样,我们可以考虑树上 dp,我们记 为以 为根的树的答案, 代表 的儿子的集合,则有:
我们考虑将原字串每一对括号视为一个节点,每个节点的父亲是在原串上最小的能够包含节点代表的括号的括号对应的节点(即 是 的父亲节点,当前仅当 对应的括号包含 对应的括号,且不存在任何括号被 包含且包含 ),形成一棵树。
这样,我们可以考虑树上 dp,我们记 为以 为根的树的答案, 代表 的儿子的集合,则有:
其中前面的求和是子树内答案,后面的统计在这个括号内一层的括号的答案。
可以建一个虚拟根节点方便统计答案。
考虑操作
考虑操作
考虑撤销操作,若该撤销使字符串少一对括号,相当于删除对应节点并将该节点所有子节点连到被删除节点处。若该撤销使字符串多一对括号(或者说恢复操作),相当于将从该店连到其父亲的子节点从其父亲中删去并连到该点上,再有该店连向其父亲。
为处理撤销操作,我们定义 代表该节点为他父节点贡献的子节点个数,则当该节点未删去时,有:
考虑操作
S(),相当于自己建一个新的节点,向虚拟根节点连边。考虑操作
(S),可以将虚拟根节点当作这个节点,并新建一个新的虚拟根节点由该点连边。考虑撤销操作,若该撤销使字符串少一对括号,相当于删除对应节点并将该节点所有子节点连到被删除节点处。若该撤销使字符串多一对括号(或者说恢复操作),相当于将从该店连到其父亲的子节点从其父亲中删去并连到该点上,再有该店连向其父亲。
为处理撤销操作,我们定义 代表该节点为他父节点贡献的子节点个数,则当该节点未删去时,有:
当该节点删去时,有:
我们可以在每次修改后进行整条到根节点路径的更新。时间平均复杂度 。
Optimization
首先,所有节点可以先将 给维护起来,这样就可以方便的删除一个节点。
其次,对于一次删除或恢复,可以先用栈维护该节点到根节点的路径,取消该路径上的贡献,更改后在恢复贡献,这样这个算法的时间平均复杂度就降到 了。
此外,我们发现,没有必要对每个节点维护 ,可以发现答案等于 ,我们可以单独维护一个答案变量。
有了上面的优化后,转移就只有
其次,对于一次删除或恢复,可以先用栈维护该节点到根节点的路径,取消该路径上的贡献,更改后在恢复贡献,这样这个算法的时间平均复杂度就降到 了。
此外,我们发现,没有必要对每个节点维护 ,可以发现答案等于 ,我们可以单独维护一个答案变量。
有了上面的优化后,转移就只有
这时我们发现,当一个节点未被删除时,它子树内的删数并不会影响到这个结点的祖先(因为这个的 值始终为 )这时,我们可以将之前维护的到根节点的链改为到最近的未被删除的点即可,可以发现,这条链的期望值不会很大(大概只有 左右),所以在随机数据下这个做法是 的。
Code
贴上我丑陋的代码(勿喷 qwq
CPP#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 数据,可按如下方法构造:
CPP 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";
但是,这个算法在随机情况下是 的,足以高速的通过此题。
相关推荐
评论
共 17 条评论,欢迎与作者交流。
正在加载评论...