题目
Description
现在我们要维护一些字符串,每个字符串都有一个值。初始的时候我们一个字符串都没有。一共有四种操作:
-
I s v. 加上一个值为v (1≤v≤100000000) 的字符串 s。注意,相同的字符串可以同时存在多个。
-
D s. 删掉所有字符串 s,如果不存在,输出 "Not Exist"。
-
Q s. 输出所有以 s 为前缀的字符串的值的和。如果不存在这样的字符串,输出 0。
-
U s t. 对于所有形式为 sx 的字符串, 把他们修改成 tx, 其中 xx 是任意字符串,可以为空。如果不存在这样的字符串,输出 "Not Exist"。数据保证 s 和 t 的最长公共前缀为空。
注意在这个题目里我们考虑的仅仅是 01 串,也就是说所有串中不存在除了 0 和 1 的元素。读入的字符串不存在空串。
Input
第一行一个整数 test (1≤test≤10) 表示数据组数。
对于每组数据,第一行一个整数 n (1≤n≤300000) 表示操作数目。
接下来 n 行,每行描述了题目中的一种操作。
对于每组数据,读入中所有字符串的总长度不超过 1000000
思路来源
官方题解:https://namomo.top:8081/blog/entry/12
题解
本题在trie上维护包含某个前缀的串的值的和,直接暴力即可。
下面介绍如何分析均摊复杂度,即证明每个输入的字符只会产生常数的运行时间。
插入字符串时,从trie的根开始,每个字符会往下走一步,如果没有对应的点会新建一个点。
沿途加上这次插入的串的值即可。这样每个字符只消耗常数时间(走一步和新建一个点)。
删除类似,走到字符串对应的点后,用该点维护的值减掉它两个子树维护的值,就可以得到所有字符串s的值的和,称为d。
再从这个点走回根,沿途将维护的值减少d即可。
询问也类似,走到对应的点输出维护的值。
最后一种修改操作,由于s和t公共前缀为空,所以代表sx的串在trie中对应的子树和代表tx的串对应的子树无交。
我们找到两棵子树的根(称为s和t),递归的合并他们。
首先把s维护的值加在t维护的值上,t作为合并后的根,s可以删除。
对于s和t各自的左儿子(字符为0的儿子),
如果至少一个儿子不存在,把存在的那个儿子作为合并后的左儿子即可。
如果两个左儿子都存在,递归合并这两个左儿子。右儿子同理。
这样,每次要么递归不继续进行,
要么在递归的同时trie里面点的数量会减一(两个根合并后只剩一个)。
由于trie里每个点都是某个输入的字符创建的,所以每次递归的复杂度平摊在每个输入的字符上只有常数。
心得
题解很详细,好评QAQ
注释都加代码里了,动态插入删除,用了个deque存了当前可以用的下标
以为自己补这种题要WA一晚上,好在WA了一发就过了
代码是根据题解自己乱搞的,比较搓比较长QAQ
这个均摊复杂度比较惊艳吧QAQ
一看题解感觉没什么,但是自己想不到
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int T,n,ch[N][2],len,c;
deque<int>q;
char s[5],w[N],t[N];
ll sum[N],v;//直接根据第三问维护 对每个前缀都加
void add(char s[],int n,ll v){
int rt=0;
for(int i=0;i<n;++i){
int x=s[i]-'0';
if(!ch[rt][x]){
c=q.front();q.pop_front();//循环利用节点 可删 可开
ch[rt][x]=c;
memset(ch[c],0,sizeof ch[c]);
sum[c]=0;
}
rt=ch[rt][x];
sum[rt]+=v;
}
}
void del(char s[],int n){
int rt=0;
for(int i=0;i<n;++i){
int x=s[i]-'0';
if(!ch[rt][x]){
puts("Not Exist");
return;
}
rt=ch[rt][x];
}
ll v=sum[rt],vls=0,vrs=0;
if(ch[rt][0])vls=sum[ch[rt][0]];
if(ch[rt][1])vrs=sum[ch[rt][1]];
v-=vls+vrs;//根据前缀和做差 答案等于s这个点减左右子树两个点之和
if(v==0){
puts("Not Exist");
return;
}
rt=0;
for(int i=0;i<n;++i){
int x=s[i]-'0';
rt=ch[rt][x];
sum[rt]-=v;
}
}
ll find(char s[],int n){
int rt=0;
for(int i=0;i<n;++i){
int x=s[i]-'0';
if(!ch[rt][x]){
return 0;
}
rt=ch[rt][x];
}
return sum[rt];
}
int dfs(int x,int y){//合并x节点和y节点所在子树
sum[y]+=sum[x];
if(ch[x][0] && ch[y][0]){
ch[y][0]=dfs(ch[x][0],ch[y][0]);
q.push_back(ch[x][0]);ch[x][0]=0;//删掉ch[x][0]并切断连接
}
else if(ch[x][0]){
ch[y][0]=ch[x][0];
ch[x][0]=0;
}
if(ch[x][1] && ch[y][1]){
ch[y][1]=dfs(ch[x][1],ch[y][1]);
q.push_back(ch[x][1]);ch[x][1]=0;
}
else if(ch[x][1]){
ch[y][1]=ch[x][1];
ch[x][1]=0;
}
return y;
}
void chg(char s[],int n,char t[],int m){
int rt=0;
for(int i=0;i<n;++i){
int x=s[i]-'0';
if(!ch[rt][x]){
puts("Not Exist");
return;
}
rt=ch[rt][x];
}
ll v=sum[rt];
if(v==0){
puts("Not Exist");
return;
}
//找到s的rt的位置
int rts=rt;
rt=0;
for(int i=0;i<n-1;++i){
int x=s[i]-'0';
rt=ch[rt][x];
sum[rt]-=v;//把s的非rt前缀减去v
}
ch[rt][s[n-1]-'0']=0;//把s子树从其父亲处删除
rt=0;
for(int i=0;i<m;++i){
int x=t[i]-'0';
if(!ch[rt][x]){
c=q.front();q.pop_front();
ch[rt][x]=c;
memset(ch[c],0,sizeof ch[c]);
sum[c]=0;
}
rt=ch[rt][x];
if(i<m-1)sum[rt]+=v;//把t的非rt前缀加上v
}
dfs(rts,rt);//暴力合并s的rt和t的rt
}
int main(){
scanf("%d",&T);
for(int ca=1;ca<=T;++ca){
while(!q.empty())q.pop_front();
for(int i=1;i<N;++i)q.push_back(i);
memset(ch[0],0,sizeof ch[0]);
sum[0]=0;
scanf("%d",&n);
printf("Case %d\n",ca);
for(int i=1;i<=n;++i){
scanf("%s%s",s,w);
char op=s[0];
if(op=='I'){
scanf("%lld",&v);
add(w,strlen(w),v);
}
else if(op=='D'){
del(w,strlen(w));
}
else if(op=='Q'){
printf("%lld\n",find(w,strlen(w)));
}
else if(op=='U'){
scanf("%s",t);
chg(w,strlen(w),t,strlen(t));
}
}
}
return 0;
}