バイナリグループ化は、実際には、大まかなスポットを描き、その原料を下回っています。
簡単にそれぞれが唯一のもの組み込まれたことを示すために\(\ログ\)があるので、時間\(\ \ログ)私たちは彼の抽象的複雑さを理解することができるように、層がある(nは\ \ログイン\ nは\ \) されます。
そして、この質問の練習について話、我々はあなたが合併の複雑さを行うたびに私たちができることを、あなたは情報のトライを置くことができるでもそう結合され、その後、各点のACオートマトンのために再び建てつまり、制御可能であることを見つけますオンライン行います。
サブクエリ文字列の数については、私たちはうまく彼らの最後の番号を失敗するルートからツリーを照会する必要があります。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 53;
char s[maxn];
int ch[maxn][26], ed[maxn], fail[maxn], tr[maxn][26], siz[maxn];
int cnt = 1;
struct ACAM {
int rt, sz;
void ins(char* s) {
int p = rt = ++cnt;
sz = 1;
while (*s) {
int c = (*s++) - 'a';
if (!ch[p][c]) ch[p][c] = ++cnt;
p = ch[p][c];
}
ed[p] = 1;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++)
if (ch[rt][i]) {
fail[tr[rt][i] = ch[rt][i]] = rt;
q.push(tr[rt][i]);
} else {
tr[rt][i] = rt;
}
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (ch[u][i]) {
fail[tr[u][i] = ch[u][i]] = tr[fail[u]][i];
q.push(tr[u][i]);
} else {
tr[u][i] = tr[fail[u]][i];
}
}
siz[u] = ed[u] + siz[fail[u]];
}
}
};
int merge(int x, int y) {
if (!x || !y) return x | y;
ed[x] += ed[y];
for (int i = 0; i < 26; i++) ch[x][i] = merge(ch[x][i], ch[y][i]);
return x;
}
struct ACAutoMaton {
ACAM rt[maxn];
int top;
ACAutoMaton() { top = 0; }
void solve(char* s) {
rt[++top].ins(s);
while (top > 1 && rt[top].sz == rt[top - 1].sz) {
rt[top - 1].rt = merge(rt[top - 1].rt, rt[top].rt);
rt[top - 1].sz += rt[top].sz, top--;
}
rt[top].build();
}
int qry(char* s) {
int ans = 0;
for (int i = 1; i <= top; i++) {
int p = rt[i].rt;
for (char* t = s; *t;) p = tr[p][(*t++) - 'a'], ans += siz[p];
}
return ans;
}
} INS, DEL;
signed main() {
int _;
scanf("%d", &_);
while (_--) {
int op;
scanf("%d", &op), scanf("%s", s);
if (op == 1) {
INS.solve(s);
}
if (op == 2) {
DEL.solve(s);
}
if (op == 3) {
printf("%d\n", INS.qry(s) - DEL.qry(s));
fflush(stdout);
}
}
return 0;
}