単語の頻度/単語
トピックリンク:ybt高効率アドバンス2-5-2 / luogu P3966
一般的なアイデア
n個の単語があり、各単語がこれらの単語に何回出現するかを尋ねます。
アイデア
一致する単語が表示され、次に複数の一致が表示される場合、ACオートマトンについて考えます。
しかし、ACオートマトンは、単語に複数の単語が現れる回数であり、過去を直接列挙することはできません。
それは実際にはかなり良いです。それを挿入するときは、前のものがいくつ挿入されていても、それに出現回数を追加します。
このように、失敗しながらこのポイントまで走るたびに、この位置から前の位置までの距離が一致する文字列でなければならないことを意味します。
次に、逆トポロジカル順序を使用して、保険のフェイルエッジツリーでプレフィックスの合計を見つけることにより、答えを得ることができます。
単語を挿入するときは、単語の終わりの位置を記録するだけで済みます。
コード
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
struct Trie {
int son[31], num, fail;
}tree[1000301];
int n, tot, dy[201];
int tp[1000301], tmp;
char c[1000001];
queue <int> q;
void insert(int op) {
//建 Trie 树
int size = strlen(c);
int now = 0;
for (int i = 0; i < size; i++) {
int nxt = c[i] - 'a';
if (!tree[now].son[nxt]) tree[now].son[nxt] = ++tot;
tree[now].num++;
now = tree[now].son[nxt];
}
tree[now].num++;
dy[op] = now;//记录答案的位置
}
void make_fail() {
//建 AC 自动机的 fail 边
for (int i = 0; i < 26; i++)
if (tree[0].son[i]) {
q.push(tree[0].son[i]);
tp[++tmp] = tree[0].son[i];
tree[tree[0].son[i]].fail = 0;
}
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = 0; i < 26; i++)
if (tree[now].son[i]) {
q.push(tree[now].son[i]);
tp[++tmp] = tree[now].son[i];
tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
}
else tree[now].son[i] = tree[tree[now].fail].son[i];
}
}
void work_tp() {
//直接跑逆序拓扑
for (int i = tmp; i >= 1; i--) {
tree[tree[tp[i]].fail].num += tree[tp[i]].num;
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", &c);
insert(i);
}
make_fail();
work_tp();
for (int i = 1; i <= n; i++)
printf("%d\n", tree[dy[i]].num);
return 0;
}