[COCI 2015/2016 #5] OOP 题解
前言
题目链接:洛谷。
本题做法有很多,可以 ACAM,可以直接变成二维数点,可以 Trie+dfs 序差分。
本题解类似最后一种做法,但是统计信息的方式略有差别,虽然时间复杂度较劣,但是支持不可差分信息,具有一定扩展性。
题意简述
给定 \(n\) 个文本串 \(s_i\) 和 \(q\) 次查询,每次给出一个模式串 \(t_i\),包含且仅包含一个 \(\tt*\),其能匹配 \(s_i\) 当且仅当将 \(\tt*\) 替换为一个字符串(可以为空)后和 \(s_i\) 相等。对于每次查询求出其能匹配多少个文本串。
题目分析
将 \(n\) 个文本串插入到一棵 Trie 中。将询问拆分为 \(\tt*\) 前后两部分,根据前半部分找到对应 Trie 中结点,问题转变为了子树中终止结点对应的文本串中,有多少文本串的后缀和询问的后半部分相等。
直接这样做是错的,考虑统计到的某个文本串前后缀有交,是不合法的,但是会被统计,例如 \(\tt ab*ba\) 不能匹配 \(\tt aba\),但会被统计。
相当于查询的时候,不能在整个文本串中找到一个后缀,而是只能从叶子到当前结点对应的字符串中,找到是否存在后缀和查询的相等。
考虑用另一棵 Trie 支持插入、查询,然后做树上启发式合并。每次继承重儿子的 Trie,并在这个 Trie 的所有叶子上新增一条边,对应当前结点到重儿子的边。
考虑分析时间复杂度。一个字符串从对应结点开始,每次当做重儿子继承是常数时间,总的时间为串长;当做轻儿子暴力插入是串长减去那个点的深度,放缩不妨当做串长算,由于当做轻儿子的次数最多对数次,因此总体是线性对数。因此,时间复杂度关于总串长线性对数。空间复杂度总串长乘字符集。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = 3e6 + 10, C = 26;
int n, q, ans[N];
string s[N], t[N];
char _s[M];
int tr2[M][C], tot2, siz2[M];
int dst[M], dtop;
inline int G() {
int u = dtop ? dst[dtop--] : ++tot2;
siz2[u] = 0;
for (int i = 0; i < C; ++i) {
if (tr2[u][i]) {
dst[++dtop] = tr2[u][i];
tr2[u][i] = 0;
}
}
return u;
}
inline void del(int u) {
dst[++dtop] = u;
}
int tr[M][C], tot;
vector<int> tail[M], qry[M];
inline void ins(char s[], int id) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int x = s[i] - 'a';
if (!tr[u][x]) tr[u][x] = ++tot;
u = tr[u][x];
}
tail[u].emplace_back(id);
}
inline void insq(char const s[], int id) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int x = s[i] - 'a';
if (!tr[u][x]) return;
u = tr[u][x];
}
qry[u].emplace_back(id);
}
int siz[M], son[M];
void dfs(int u) {
siz[u] = 1;
son[u] = -1;
int mxsiz = 0;
for (int i = 0; i < C; ++i) {
int v = tr[u][i];
if (!v) continue;
dfs(v);
siz[u] += siz[v];
if (siz[v] > mxsiz) {
mxsiz = siz[v];
son[u] = i;
}
}
}
void doins(int u, int &rt, vector<int> &leaf, int d) {
for (int i : tail[u]) {
const string &s = ::s[i];
int len = s.length();
if (!rt) rt = G();
int cur = rt;
++siz2[cur];
for (int i = len - 1; i >= d; --i) {
int x = s[i] - 'a';
if (!tr2[cur][x]) {
tr2[cur][x] = G();
}
cur = tr2[cur][x];
++siz2[cur];
}
leaf.emplace_back(cur);
}
}
void INS(int u, int &rt, vector<int> &leaf, int d) {
for (int i = 0; i < C; ++i) {
int v = tr[u][i];
if (!v) continue;
INS(v, rt, leaf, d);
}
doins(u, rt, leaf, d);
}
pair<int, vector<int>> redfs(int u, int d) {
for (int i = 0; i < C; ++i) {
int v = tr[u][i];
if (!v) continue;
if (i == son[u]) continue;
auto [rt, leaf] = redfs(v, d + 1);
if (rt) {
del(rt);
}
}
int rt_u = 0;
vector<int> leaf_u;
if (son[u] != -1) {
int i = son[u], v = tr[u][i];
auto [rt, leaf] = redfs(v, d + 1);
rt_u = rt;
for (int uu : leaf) {
if (!tr2[uu][i]) {
tr2[uu][i] = G();
}
leaf_u.emplace_back(tr2[uu][i]);
++siz2[tr2[uu][i]];
}
}
for (int i = 0; i < C; ++i) {
int v = tr[u][i];
if (!v) continue;
if (i == son[u]) continue;
INS(v, rt_u, leaf_u, d);
}
doins(u, rt_u, leaf_u, d);
if (rt_u) {
for (int qi : qry[u]) {
int cur = rt_u;
bool flag = false;
for (int i = 0; i < (int)t[qi].length(); ++i) {
int x = t[qi][i] - 'a';
if (!tr2[cur][x]) {
flag = true;
break;
}
cur = tr2[cur][x];
}
if (flag) {
continue;
}
ans[qi] = siz2[cur];
}
}
return { rt_u, leaf_u };
}
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) {
scanf("%s", _s);
s[i] = string(_s);
ins(_s, i);
}
for (int i = 1; i <= q; ++i) {
scanf("%s", _s);
int j = 0;
while (_s[j] != '*') ++j;
string L(_s, _s + j), R(_s + j + 1, _s + strlen(_s));
reverse(R.begin(), R.end());
t[i] = R;
insq(L.c_str(), i);
}
dfs(0);
redfs(0, 0);
for (int i = 1; i <= q; ++i) {
printf("%d\n", ans[i]);
}
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/19011410。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号