题意:给出n个串作为字典,和q个查询,每个查询包含一个前缀和一个后缀,问字典中有多少串与查询给出的前后缀匹配。
绝了这个做法。
ac自动机在失配的时候,会自动跳转到当前在ac自动机上跑的串能够匹配的最长后缀位置,这一条性质竟然能搞出这么骚的操作。
将查询的前后缀以后缀 + “{” + 前缀的形式拼在一起(之所以用大右括号是因为它是小写字母z的ascii码下一个),全部建立ac自动机,然后将字典串变成原串 + “{” + 原串的形式一个一个放到ac自动机上跑,随着失配,自然前半部分就会落到其在ac自动机中能够匹配的最长后缀位置,这就完成了后缀的匹配,之后一路跑下去,跨过大括号的部分自然也就完成了前缀的匹配。同时因为题目保证给出的查询前后缀一定比原串短,所以只能选取前后缀长度和比字典串小至少2的的查询串(前后缀各自最少会少一个字符)。
这个过程自然也能用fail树更新来优化复杂度(避免在跑的时候每次对一条链进行更新)。
真的nb这个做法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 500005;
int t, n, m;
string in[100005];
struct AC_Automaton {
int next[maxn][27];
int fail[maxn];
vector<int> endpos;
int que[maxn], qt, qh;
int ans[maxn], len[maxn];
int sz, root;
int newNode() {
for (int i = 0; i < 27; i++) {
next[sz][i] = -1;
}
len[sz] = ans[sz] = 0;
fail[sz] = -1;
return sz++;
}
void init() {
sz = 0;
qt = 0, qh = 1;
endpos.clear();
memset(ans, 0, sizeof(ans));
root = newNode();
}
void add(string s) {
int p = root, c;
for (int i = 0; i < s.length(); i++) {
c = s[i] - 'a';
if (next[p][c] == -1) {
next[p][c] = newNode();
len[next[p][c]] = i + 1;
}
p = next[p][c];
}
endpos.push_back(p);
}
void getFail() {
for (int i = 0; i < 27; i++) {
if (~next[root][i]) {
fail[next[root][i]] = root;
que[++qt] = next[root][i];
} else {
next[root][i] = root;
}
}
while (qh <= qt) {
int p = que[qh++];
for (int i = 0; i < 27; i++) {
if (~next[p][i]) {
fail[next[p][i]] = next[fail[p]][i];
que[++qt] = next[p][i];
} else {
next[p][i] = next[fail[p]][i];
}
}
}
}
void solve(int x) {
int p = root;
for (int i = 0; i < in[x].length(); ++i) {
int c = in[x][i] - 'a';
p = next[p][c];
while(len[p] > in[x].length() / 2 + 1){
p = fail[p];
}
++ans[p];
}
}
void update(){
for (int i = sz; i > root; --i) {
int e = que[i];
ans[fail[e]] += ans[e];
}
}
} ac;
int main() {
ios::sync_with_stdio(0);
cin >> t;
while (t--) {
cin >> n >> m;
ac.init();
for (int i = 1; i <= n; ++i) {
cin >> in[i];
in[i] += "{" + in[i];
}
string a, b;
for (int i = 1; i <= m; ++i) {
cin >> a >> b;
ac.add(b + "{" + a);
}
ac.getFail();
for (int i = 1; i <= n; ++i) {
ac.solve(i);
}
ac.update();
for (auto i : ac.endpos) {
cout << ac.ans[i] << '\n';
}
}
return 0;
}