题目链接:
https://nanti.jisuanke.com/t/26987
题目大意
实现一个程序,支持添加字符串,删除以某串为后缀的字符串,修改以某串为后缀的字符串为另一个后缀,查询以某串为后缀的字符串的个数,查询某个版本时以某串为后缀的字符串的个数。
题解
处理后缀的问题我们翻转字符串就变成处理前缀了。显然我们需要可持久化Trie实现。本篇只是贴板子。
由于题目保证所有插入删除的操作输入的字符串总长度不超过1e6
,所以我们为可持久化Trie开设这么多的空间即可,因为每操作一次,可持久化Trie就会新增以操作字符串长度为个数的节点。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = int(1e6) + 10, V = int(1e5) + 10;
void *allocate();
struct Trie
{
Trie *child[26];
ll cnt, word;
Trie()
{
for (int i = 0; i < 26; ++i)
child[i] = nullptr;
cnt = word = 0;
}
// 我们预先开设内存池,如果使用new和delete的话是很慢的
// 因为动态申请空间耗时比较大。
void *operator new(size_t)
{
return allocate();
}
Trie *&ch(char ch)
{
return child[ch - 'a'];
}
// 查找表示str的节点
Trie *find(const char *str)
{
if (!*str)
return this;
else if (ch(*str) == nullptr)
return nullptr;
else
return ch(*str)->find(str + 1);
}
// 插入str,个数为count
static Trie *insert(Trie *node, const char *str, ll count)
{
Trie *here = node == nullptr ? new Trie() : new Trie(*node);
here->cnt += count;
if (!*str)
here->word += count;
else if (here->ch(*str) == nullptr)
here->ch(*str) = insert(nullptr, str + 1, count);
else
here->ch(*str) = insert(here->ch(*str), str + 1, count);
return here;
}
// 查询有多少个以str为前缀的字符串
ll query(const char *str)
{
Trie *node = find(str);
return node == nullptr ? 0 : node->cnt;
}
// 删除str,返回值为新的Trie树和删除了多少个str。
pair<Trie *, ll> remove(const char *str)
{
if (!*str)
{
Trie *here = new Trie(*this);
here->cnt -= word;
here->word = 0;
return {here, word};
}
else if (ch(*str) == nullptr)
return {nullptr, 0};
else
{
auto res = ch(*str)->remove(str + 1);
if (!res.second)
return {nullptr, 0};
Trie *here = new Trie(*this);
here->ch(*str) = res.first;
here->cnt -= res.second;
return {here, res.second};
}
}
// 删除以str为前缀的所有字符串,实际作用是删除某个节点及其子节点,
// 并更新祖先的cnt,服务update操作。
pair<Trie *, ll> remove_prefix(const char *str)
{
if (!*str)
{
Trie *here = new Trie();
return {here, cnt};
}
else if (ch(*str) == nullptr)
return {nullptr, 0};
else
{
auto res = ch(*str)->remove_prefix(str + 1);
Trie *here = new Trie(*this);
here->ch(*str) = res.first;
here->cnt -= res.second;
return {here, res.second};
}
}
// 将以from为前缀的字符串替换为以to为前缀
// 原理是将from节点的子树提取出来转接到to节点
// 返回新的Trie和update的状态,-1为Empty,0为Conflict,1为成功修改。
static pair<Trie *, int> update(Trie *rt, const char *from, const char *to)
{
Trie *f = rt->find(from), *t = rt->find(to);
if (f == nullptr || f->cnt == 0)
return {nullptr, -1};
else if (t != nullptr && t->cnt)
return {nullptr, 0};
else
{
Trie x = *f;
// 我们转接了from节点的子树后,要更新祖先节点的cnt。
rt = rt->remove_prefix(from).first;
// 我们更新to节点的祖先的cnt。
rt = insert(rt, to, x.cnt);
// 将to节点更新为from节点的信息
*rt->find(to) = x;
return {rt, 1};
}
}
} *ver[V], pool[N * 2], *poolC;
void *allocate()
{
return poolC++;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T, n, v;
ll c;
string str, op, to;
cin >> T;
while (T--)
{
poolC = pool;
ver[0] = new Trie();
v = 0;
cin >> n;
while (n--)
{
cin >> op >> str;
reverse(str.begin(), str.end());
if (op == "insert")
{
cin >> c;
++v;
ver[v] = Trie::insert(ver[v - 1], str.c_str(), c);
}
else if (op == "query")
{
ll res = ver[v]->query(str.c_str());
cout << res << endl;
}
else if (op == "delete")
{
auto res = ver[v]->remove(str.c_str());
if (!res.second)
cout << "Empty" << endl;
else
ver[++v] = res.first;
}
else if (op == "update")
{
cin >> to;
reverse(to.begin(), to.end());
auto res = Trie::update(ver[v], str.c_str(), to.c_str());
if (res.second == -1)
cout << "Empty" << endl;
else if (res.second == 0)
cout << "Conflict" << endl;
else
ver[++v] = res.first;
}
else if (op == "vquery")
{
cin >> c;
cout << ver[v - c]->query(str.c_str()) << endl;
}
}
}
return 0;
}