字符串杂记 2025.8.4始
字符串的原罪
这篇内容不介绍模板,纯粹题解。
1.1 Hash
1.2 KMP
1.3 AC自动机
1.3.1 [ABC268Ex] Taboo
問題文
文字列 \(S\) が与えられます。また、高橋君は次の操作を \(0\) 回以上行うことが出来ます。
- \(1 \leq i \leq |S|\) なる整数 \(i\) を選び、\(S\) の \(i\) 文字目を
*に変える。高橋君の目的は、\(S\) の部分文字列として \(N\) 個の文字列 \(T_1,T_2,\ldots,T_N\) がいずれも現れないようにすることです。
これを達成するために必要な操作の回数の最小値を求めてください。问题陈述。
给定字符串 \(S\) 。此外,高桥可以多次执行以下操作 \(0\) 。
- 选择一个与 \(1 \leq i \leq |S|\) 相等的整数 \(i\) 并将 \(S\) 中的 \(i\) 字符改为
*。高桥的目标是确保 \(N\) 字符串 \(T_1,T_2,\ldots,T_N\) 中没有一个字符串作为 \(S\) 的 ** 子串出现。
请找出实现这一目标所需的最少操作数。
显然如果按照右端点排序并且匹配的话,在每个字符串末尾打一个 \(*\) 号是一定更好的。
有了上面的贪心,多模式串,可以考虑 \(\textbf{AC自动机}\) 求解。
在打 \(*\) 号之后,将当前指针置于根节点就可以重新开始匹配啦~!
我们知道,匹配的时候,如果一位一位地匹配,每次都需要跳 \(fail\),很麻烦,可以下传是否有匹配,见代码。
\(\huge \mathscr{Code}\)
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 100;
int n, m, tot, ans;
struct node {
int val, fail, son[26];
}AC[N];
string s, pat[N];
void Insert(string a) {
int cur = 0;
for (int i = 0; i < a.size(); i++) {
int ch = a[i] - 'a';
if (!AC[cur].son[ch]) AC[cur].son[ch] = ++tot;
cur = AC[cur].son[ch];
}
AC[cur].val = 1;
}
void Getfail() {
queue<int> q;
for (int i = 0; i < 26; i++) if (AC[0].son[i]) q.push(AC[0].son[i]);
while (!q.empty()) {
int cur = q.front();
q.pop();
AC[cur].val += AC[AC[cur].fail].val;
for (int i = 0; i < 26; i++) {
if (AC[cur].son[i]) {
AC[AC[cur].son[i]].fail = AC[AC[cur].fail].son[i];
q.push(AC[cur].son[i]);
}
else AC[cur].son[i] = AC[AC[cur].fail].son[i];
}
}
}
void Query() {
int cur = 0;
for (int i = 0; i < s.size(); i++) {
int ch = s[i] - 'a';
cur = AC[cur].son[ch];
if (AC[cur].val) ans++, cur = 0;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> s >> n;
for (int i = 1; i <= n; i++) {
cin >> pat[i];
Insert(pat[i]);
}
Getfail();
Query();
cout << ans;
return 0;
}

浙公网安备 33010602011771号