Wannafly挑战赛14B.前缀查询(Trie+类线段树操作)

链接:https://ac.nowcoder.com/acm/contest/81/B
来源:牛客网
 

前缀查询

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

在一个 Minecraft 村庄中,村长有这一本小写字母构成的名册(字符串的表),
每个名字旁边都记录着这位村民的声望值,而且有的村民还和别人同名。
随着时间的推移,因为没有村民死亡,这个名册变得十分大。

现在需要您来帮忙维护这个名册,支持下列 4 种操作:

1. 插入新人名 si,声望为 ai
2. 给定名字前缀 pi 的所有人的声望值变化 di
3. 查询名字为 sj 村民们的声望值的和(因为会有重名的)
4. 查询名字前缀为 pj 的声望值的和

输入描述:

第一行为两个整数 0 ≤ N ≤ 105,表示接下来有 N 个操作;
接下来 N 行,每行输入一个操作,行首为一个整数 1 ≤ oi ≤ 4,表示这一行的操作的种类,

那么这一行的操作和格式为:

1. 插入人名,这一行的格式为 1 si ai,其中 |ai| ≤ 103
2. 前缀修改声望,这一行的格式为 2 pi di,其中 |di| ≤ 103
3. 查询名字的声望和,这一行的格式为 3 sj
4. 查询前缀的声望和,这一行的格式为 4 pj

输入保证插入人名的字符串的长度和小于或等于 105,总的字符串的长度和小于或等于 106。

输出描述:

对于每一次询问操作,在一行里面输出答案。

示例1

输入

复制

20
1 a -10
1 abcba -9
1 abcbacd 5
4 a
2 a 9
3 aadaa
3 abcbacd
4 a
3 a
2 a 10
3 a
2 a -2
2 d -8
1 ab -2
2 ab -7
1 aadaa -3
4 a
3 abcba
4 a
4 c

输出

复制

-14
0
14
13
-1
9
11
1
11
0

这题有一个地方值得我理解的就是,Trie树这种结构其实就可以理解为线段树的结构,因为我们可以在Trie树上打上标记,这样就可以对树上的信息进行维护和修改,比如此题中我们就可以在树节点上做lazy标记,这样修改前缀,我们只需要找到那个子树就可以了。

线段树也可以看作是二进制数的 Trie,可以发现这两种数据结构也是有相同之处的,理解了这一点,这个题就变得很简单了。

#include <bits/stdc++.h>
using namespace std;
const int maxl = 1000000 + 5, maxn = 100000 + 5;
typedef long long ll;
struct Trie {
    struct state {
        int nxt[26];
        int fa;
        int cnt;
        ll sum, lzy;
    }st[maxl];
     
    int size;
     
    void init() {
        memset(&st, 0, sizeof st);
        size = 1;
    }
    void pushdown(int now) {
        if (!now || !st[now].lzy)return;
        ll lzy = st[now].lzy;
        for (int j = 0; j < 26; j++) if (st[now].nxt[j])st[st[now].nxt[j]].lzy+=lzy;
        st[now].sum += st[now].lzy * st[now].cnt;
        st[now].lzy = 0;
    }
    void insert(char* s, int v) {
        int n = strlen(s);
        int now = 0;
        for (int i = 0; i < n; i++) {
            int c = s[i] - 'a';
            if (!st[now].nxt[c]) {
                st[now].nxt[c] = size++;
                st[size-1].fa = now;
            }
            now = st[now].nxt[c];
            pushdown(now);
            st[now].cnt++;
            st[now].sum += v;
        }
    }
     
    void update(int now, ll s) {
        while (now) {
            st[now].sum += s;
            now = st[now].fa;
        }
    }
    void add(char* s, int v) {
        int n = strlen(s);
        int now = 0;
        for (int i = 0; i < n; i++) {
            int c = s[i] - 'a';
            now = st[now].nxt[c];
            if (!now) return;
            pushdown(now);
        }
        st[now].lzy += v;
        //cout << st[now].fa << "#" << endl;
        update(st[now].fa, st[now].cnt * (ll)v);
    }
     
    ll queryall(char* s) {
        int n = strlen(s);
        int now = 0;
        for (int i = 0; i < n; i++) {
            int c = s[i] - 'a';
            now = st[now].nxt[c];
            if (now == 0) return 0;
            pushdown(now);
        }
        return st[now].sum;
    }
     
    ll queryself(char* s) {
        int n = strlen(s);
        int now = 0;
        for (int i = 0; i < n; i++) {
            int c = s[i] - 'a';
            now = st[now].nxt[c];
            if (now == 0) return 0;
            pushdown(now);
        }
        ll ans = st[now].sum;
        for (int i = 0; i < 26; i++) {
            pushdown(st[now].nxt[i]);
            if (st[st[now].nxt[i]].sum) ans -= st[st[now].nxt[i]].sum;
        }
        return ans;
    }
     
} trie;
char s[maxl];
int main(){
    //freopen("a.txt", "r", stdin);
    int n;
    scanf("%d", &n);
    trie.init();
    for (int i = 0; i < n; i++) {
        int op, t;
        scanf("%d%s", &op, s);
        //cout << op << ' ' << s << endl;
        switch(op) {
            case 1:scanf("%d", &t); trie.insert(s, t); break;
            case 2:scanf("%d", &t); trie.add(s, t); break;
            case 3:printf("%lld\n", trie.queryself(s)); break;
            case 4:printf("%lld\n", trie.queryall(s)); break;
            default:break;
        }
        //for (int i = 0; i < 10; i++) cout << trie.st[i].cnt << ' '<< trie.st[i].sum << ' '<< trie.st[i].lzy << endl;
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值