Namomo Cockfight Round 5 D.String(trie 01字典树 动态插入删除 暴力合并)

题目

Description

现在我们要维护一些字符串,每个字符串都有一个值。初始的时候我们一个字符串都没有。一共有四种操作:

  1. I s v. 加上一个值为v (1≤v≤100000000) 的字符串 s。注意,相同的字符串可以同时存在多个。

  2. D s. 删掉所有字符串 s,如果不存在,输出 "Not Exist"。

  3. Q s. 输出所有以 s 为前缀的字符串的值的和。如果不存在这样的字符串,输出 0。

  4. 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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值