链接: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;
}