TP

    • KMP
    • AC自动机建树/图
  • 最后就是例题时间:
    • 搜索关键词
    • 单词
    • 设计密码
    • 修复DNA
    • Codeforces16届黑龙江省赛E题
    • 洛谷:阿狸打字机(经典自动机,fail树上数据结构维护信息)
    • [洛谷:Video Game G(简单自动机dp)](https://www.luogu.com.cn/problem/P3041)
    • 洛谷:文本生成器(dp统计方案)
    • [洛谷:Censoring G(贪心栈处理删除串问题)](https://www.luogu.com.cn/problem/P3121)
    • [洛谷:The ABCD Murderer(自动机转数据结构优化dp)](https://www.luogu.com.cn/problem/P7456)
    • [CF:You Are Given Some Strings(2400的字符串匹配方案数)](https://www.luogu.com.cn/problem/CF1202E)
    • [CF163E e-Government (fail树上,数据结构维护差分序列)](https://www.luogu.com.cn/problem/CF163E)

首先,自动机 算法是建立在字典树基础上,配合 kmp 算法进行一个多 fail 指针转移的过程。

KMP

 //ne 数组记录前 i 位的最大匹配的前后缀//ne[1] 显然是 0for (int i = 2, j = 0; i <= n; i++) {while (j && p[i] != p[j + 1])j = ne[j];if (p[i] == p[j + 1])j++;ne[i] = j;}

拓展 ↓↓↓

AC自动机建树/图

  • 字典树预处理
void insert(string& g) {int p = 0;for (int i = 0; i < sz(g); i++) {int u = g[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p] = 1;
}
  • 普通自动机:
void build() {queue<int> q;for (int i = 0; i < 4; i++)if (tr[0][i])q.push(tr[0][i]);while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 4; i++) {int p = tr[t][i];if (!p)continue;int j = ne[t];while (j && !tr[j][i])j = ne[j];j = tr[j][i];ne[p] = j;q.push(p);}}
}

根通常都设置为 0 ,这样有些 n e [ ] 就不用特别设置 根通常都设置为 0,这样有些 ne[] 就不用特别设置 根通常都设置为0,这样有些ne[]就不用特别设置
i f ( ! p ) c o n t i n u e ; if (!p)continue; if(!p)continue;
没有把不存在的点建出来 没有把不存在的点建出来 没有把不存在的点建出来

  • trie图:
void build() {queue<int> q;for (int i = 0; i < 4; i++)if (tr[0][i])q.push(tr[0][i]);while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 4; i++) {int p = tr[t][i];if (!p)tr[t][i] = tr[ne[t]][i];else {ne[p] = tr[ne[t]][i];q.push(p);}}}
}

不存在的结点直接补向了最近的 f a i l 指针 不存在的结点直接补向了最近的fail指针 不存在的结点直接补向了最近的fail指针

最后就是例题时间:

跟着神仙博客 e n 造 跟着神仙博客en造 跟着神仙博客en造

搜索关键词

记录单词数,但只记一次,清空标记即可,练手题

单词

考了一个拓扑序dp,遍历到某个结点如果存在单词,这个结点 fail 路径上的单词都出现。所以逆序递推一下。

设计密码

kmp类型多状态状态机dp,把 fail 指针的位置当作状态表示的其中一维,匹配字母时更新状态,维护情况。

修复DNA

一样的状态机dp,不过是多维kmp的模式。注意维护不可行情况。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 1e3 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;unordered_map<char, int> mp = {{'A',0},{'G',1},{'C',2},{'T',3}
};
int tr[N][4], ne[N], tdx;
bool cnt[N];//开到总长度即可,不必*4
char s[N];
int dp[N][N];void init() {for (int i = 0; i <= tdx; i++) {mem(tr[i], 0);ne[i] = cnt[i] = 0;}tdx = 0;
}void insert(string& g) {int p = 0;for (int i = 0; i < sz(g); i++) {int u = mp[g[i]];if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p] = 1;
}void build() {queue<int> q;for (int i = 0; i < 4; i++)if (tr[0][i])q.push(tr[0][i]);while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 4; i++) {int& u = tr[t][i];if (!u)u = tr[ne[t]][i];else {ne[u] = tr[ne[t]][i];q.push(u);cnt[u] |= cnt[t];//每个致病串后接的串确实也是不合法的//不加此句能过是因为,dp转移是从 0 结点开始,特判了转移点是否致病//致病点都不能被更新,自然后接的点也不会被更新到}}cnt[t] |= cnt[ne[t]];//可能致病串"不在"此串上,即不后接,但也在此串中//这样就要看看此串所有的有可能的前后缀中是否有致病串//直接dp转移bool即可}
}void solve() {int step = 0;while (cin >> n, n){init();string g;for (int i = 1; i <= n; i++) {cin >> g;insert(g);}build();cin >> s + 1;n = strlen(s + 1);mem(dp, 0x3f);dp[0][0] = 0;for (int i = 0; i < n; i++) {int su = mp[s[i + 1]];//在循环外就用变量存起来,减少时间损耗,注意是 i + 1for (int j = 0; j <= tdx; j++) {if (dp[i][j] == INF)continue;for (int u = 0; u < 4; u++) {int to = tr[j][u];if (!cnt[to])dp[i + 1][to] = min(dp[i + 1][to], dp[i][j] + (su != u));}}}int ans = INF;for (int j = 0; j <= tdx; j++)ans = min(ans, dp[n][j]);if (ans == INF)ans = -1;cout << "Case " << ++step << ": ";cout << ans << endl;}
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

Codeforces16届黑龙江省赛E题

简单d个p,观察一下发现每个结点可以由父亲和fail指针转移得来,维护max。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 5e5 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;int tr[N][26], ne[N], cnt[N], fa[N], tdx;
int dp[N];
char s[N];void init() {for (int i = 0; i <= tdx; i++) {mem(tr[i], 0);ne[i] = cnt[i] = 0;}tdx = 0;
}void insert() {int p = 0;for (int i = 0; s[i]; i++) {int u = s[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p]++;
}int build() {int mx = 0;queue<int> q;for (int i = 0; i < 26; i++)if (tr[0][i]) {q.push(tr[0][i]);}while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int p = tr[t][i];if (!p)continue;fa[p] = t;int j = ne[t];while (j && !tr[j][i])j = ne[j];j = tr[j][i];ne[p] = j;q.push(p);}dp[t] = max(dp[fa[t]], dp[ne[t]]) + cnt[t];mx = max(mx, dp[t]);}return mx;
}void solve() {cin >> n;for (int i = 1; i <= n; i++) {cin >> s;insert();}cout << build();
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

洛谷:阿狸打字机(经典自动机,fail树上数据结构维护信息)

注意一些变量的含义。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 2e5 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;int tr[N][26], ne[N], fa[N], tdx;
int id[N];//记录第 i 个被打印的字符串对应的结点 tdx
char s[N];vector<int> e[N];void build() {queue<int> q;for (int i = 0; i < 26; i++)if (tr[0][i]) {q.push(tr[0][i]);//bug ——  e[0].push_back(tr[0][i]);//会导致重边,之后dfs序时没有特判每个点只进入一次}while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int p = tr[t][i];if (!p)continue;int j = ne[t];while (j && !tr[j][i])j = ne[j];j = tr[j][i];ne[p] = j;q.push(p);}e[ne[t]].push_back(t);}
}int l[N], r[N], tim;
void dfs(int x) {l[x] = ++tim;for (int j : e[x])dfs(j);r[x] = tim;
}struct BIT
{int tr[N];inline void ub(int& x) { x += x & (-x); }inline void db(int& x) { x -= x & (-x); }inline void modify(int x, int t) { for (; x <= tim; ub(x))tr[x] += t; }inline int query(int x) {int res = 0;for (; x > 0; db(x))res += tr[x];return res;}
}bt;int ans[N];
struct node
{int x, id;
};
vector<node> qy[N];void solve() {cin >> s + 1;n = strlen(s + 1);int p = 0, add = 0;for (int i = 1; i <= n; i++) {if (s[i] == 'B') {p = fa[p];}else if (s[i] == 'P') {id[++add] = p;}else {int u = s[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;fa[tr[p][u]] = p;p = tr[p][u];}}build();dfs(0);cin >> m;for (int i = 1; i <= m; i++) {int a, b;cin >> a >> b;//存的是字典树结点编号qy[b].push_back({ id[a],i });}p = 0, add = 0;for (int i = 1; i <= n; i++) {if (s[i] == 'B') {bt.modify(l[p], -1);p = fa[p];}else if (s[i] == 'P') {++add;for (auto v : qy[add]) {int x = v.x, id = v.id;int res = bt.query(r[x]) - bt.query(l[x] - 1);ans[id] = res;}}else {int u = s[i] - 'a';p = tr[p][u];bt.modify(l[p], 1);}}forr(i, 1, m)cout << ans[i] << endl;
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

洛谷:Video Game G(简单自动机dp)

很模板,跟修复DNA是一类题。

洛谷:文本生成器(dp统计方案)

分可读不可读转移即可。

洛谷:Censoring G(贪心栈处理删除串问题)

删除字符串中出现的某些串,删除顺序是最早开始出现的位置。

注意题意:列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在 s 中出现的开始位置是互不相同的。

只通过字典树内当前结点是否有标记来判断存在删除串的不对的,因为有可能有某个最长后缀也可以删,这个信息存在fail路径上。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 1e5 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;int tr[N][26], ne[N], cnt[N], tdx;
char str[N], s[N];
int top, id[N], stk[N];void insert() {int p = 0;for (int i = 0; s[i]; i++) {int u = s[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p] = strlen(s);//每个串位置记录串大小//题意保证没有包含串
}void build() {queue<int> q;for (int i = 0; i < 26; i++)if (tr[0][i]) {q.push(tr[0][i]);}while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int &p = tr[t][i];if (!p)p = tr[ne[t]][i];else {ne[p] = tr[ne[t]][i];q.push(p);}}cnt[t] = max(cnt[t], cnt[ne[t]]);//维护一个最大的可以转移的位置,最长后缀//题意保证没有包含串的话,出现最长就删是正确的}
}void solve() {cin >> str + 1;cin >> n;for (int i = 1; i <= n; i++) {cin >> s;insert();}build();n = strlen(str + 1);int p = 0;for (int i = 1; i <= n; i++) {int u = str[i] - 'a';int to = tr[p][u];stk[++top] = i;//栈里存下标才行if (cnt[to]) {//bug —— p = id[i - cnt[to]],中间有可能已经删去,得从栈的下标中找到对应位置top -= cnt[to];p = id[stk[top]];}else p = to;id[i] = p;}for (int i = 1; i <= top; i++)cout << str[stk[i]];
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

洛谷:The ABCD Murderer(自动机转数据结构优化dp)

问题可转换成:用尽可能少的给定模式串个数,可重叠覆盖 地构建出文本串

如果当前匹配到 i 位置,i 结点 fail 路径上所有模式串都可以当作结尾,但可以发现这些模式串都是后缀匹配的关系,所以我们可以选一个长度最大的模式串。

选取之后,由于可以重叠覆盖,自然在区间 [ i − L i , i − 1 ] [i-L_i,i-1] [i−Li​,i−1] 都可以转移过来,我们可以用数据结构维护区间最小值。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 3e5 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;int tr[N][26], ne[N], cnt[N], tdx;
char str[N], s[N];void insert() {int p = 0;for (int i = 0; s[i]; i++) {int u = s[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p] = (int)strlen(s);
}void build() {queue<int> q;for (int i = 0; i < 26; i++)if (tr[0][i]) {q.push(tr[0][i]);}while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int& p = tr[t][i];if (!p)p = tr[ne[t]][i];else {ne[p] = tr[ne[t]][i];q.push(p);}}cnt[t] = max(cnt[t], cnt[ne[t]]);//维护fail路径上最长模式串}
}int dp[N];
struct segtree
{struct node {int l, r;int mi;}tr[N << 2];inline void pushup(int u) {tr[u].mi = min(tr[ul].mi, tr[ur].mi);}void build(int u, int l, int r) {tr[u] = { l,r,INF };  //assign somethingif (l == r) {return;}int mid = l + r >> 1;build(ul, l, mid), build(ur, mid + 1, r);}void modify(int u, int l, int r, int v) {if (tr[u].l >= l && tr[u].r <= r) {tr[u].mi = min(tr[u].mi, v);return;}int mid = tr[u].l + tr[u].r >> 1;if (l <= mid)modify(ul, l, r, v);if (r > mid)modify(ur, l, r, v);pushup(u);}int query(int u, int l, int r) {if (tr[u].l >= l && tr[u].r <= r) {return tr[u].mi;}int mid = tr[u].l + tr[u].r >> 1;int t = INF;if (l <= mid)t = query(ul, l, r);if (r > mid)t = min(t, query(ur, l, r));return t;}
}seg;void solve() {cin >> n;cin >> str + 1;for (int i = 1; i <= n; i++) {cin >> s;insert();}build();int p = 0;n = strlen(str + 1);seg.build(1, 0, n);mem(dp, 0x3f);dp[0] = 0;seg.modify(1, 0, 0, 0);for (int i = 1; i <= n; i++) { //标准dpint u = str[i] - 'a';p = tr[p][u];if (cnt[p]) {int l = max(0, i - cnt[p]), r = i - 1;dp[i] = seg.query(1, l, r) + 1;seg.modify(1, i, i, dp[i]);}}int ans = dp[n];//注意不能特判 ans == INFif (ans > INF / 2)ans = -1;cout << ans;
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

CF:You Are Given Some Strings(2400的字符串匹配方案数)

首先对于任意 s i + s j s_i+s_j si​+sj​ ,相当于是文本串的子串,对其切分成两部分,可以发现在计算:文本串的每个 [ 1 , i ] [1,i] [1,i] 有多少个以 i 结尾的后缀匹配模式串,乘上,对应 [ i + 1 , n ] [i+1,n] [i+1,n] 倒过来以 i+1 结尾有多少个后缀模式串

所以我们正向模式串和反向模式串分别建立正反自动机,最后在顺序遍历文本串记录f1[i],逆序记录f2[i]。答案就是枚举累计每个 f1[i] * f2[i+1]。很巧妙不愧是2400的含金量

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, ll> PII;const int N = 4e5 + 10, M = 3e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;//空间直接开两倍,别省
int tr[N][26], rtr[N][26], ne[N], cnt[N], tdx;
int f1[N], f2[N];
string str, s;void insert(int tr[][26], string &s) {int p = 0;for (int i = 0; i < sz(s); i++) {int u = s[i] - 'a';if (!tr[p][u])tr[p][u] = ++tdx;p = tr[p][u];}cnt[p]++;
}void build(int tr[][26]) {queue<int> q;for (int i = 0; i < 26; i++) {if (tr[0][i])q.push(tr[0][i]);}while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int &p = tr[t][i];if (!p)p = tr[ne[t]][i];else {ne[p] = tr[ne[t]][i];q.push(p);}}cnt[t] += cnt[ne[t]];//统计下 i 结点结尾的所有后缀模式串}
}void solve() {cin >> str;str = " " + str;cin >> n;for (int i = 1; i <= n; i++) {cin >> s;insert(tr, s);reverse(all(s));insert(rtr, s);}build(tr), build(rtr);int p = 0;n = sz(str) - 1;for (int i = 1; i <= n; i++) {int u = str[i] - 'a';p = tr[p][u];//bug —— tr[u][p] 典f1[i] = cnt[p];}p = 0;for (int i = n; i >= 1; i--) {int u = str[i] - 'a';p = rtr[p][u];f2[i] = cnt[p];}ll ans = 0;for (int i = 1; i < n; i++)//不能重叠,是 i 和 i + 1ans += 1ll * f1[i] * f2[i + 1];cout << ans;
}int main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

CF163E e-Government (fail树上,数据结构维护差分序列)

  • 这玩意居然 2800

  • 思路还是有的,但是想错了一个点,偏去树链剖分了

对于每次匹配就是去找该结点 fail 路径上有多少贡献点,反向想一下,每个贡献点可以对自己 fail 子树内所有点 贡献 1,这样就可以差分区间维护、单点修改了。

#include<bits/stdc++.h>
#include<unordered_map>
#define debug cout << "debug---  "
#define debug_ cout << "\n---debug---\n"
#define oper(a) operator<(const a& ee)const
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll, int> PII;const int N = 1e6 + 10, M = 1e5 + 10, mod = 1e9 + 7;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, B = 10, ki;int tr[N][26], ne[N], idx;
int id[M];
bool st[M];vector<int> fail[N];
int l[N], r[N], tim;int insert(string & t) {int p = 0;for (int i = 0; i < sz(t); i++) {int u = t[i] - 'a';if (!tr[p][u])tr[p][u] = ++idx;p = tr[p][u];}return p;
}void build() {queue<int> q;for (int i = 0; i < 26; i++)if (tr[0][i])q.push(tr[0][i]);while (q.size()){int t = q.front();q.pop();for (int i = 0; i < 26; i++) {int p = tr[t][i];if (!p)tr[t][i] = tr[ne[t]][i];else {ne[p] = tr[ne[t]][i];q.push(p);}}fail[ne[t]].push_back(t);}
}//BIT维护差分数组,区间修改、单点查询
struct BIT
{int tr[N];inline void ub(int& x) { x += x & (-x); }inline void db(int& x) { x -= x & (-x); }void modify(int x, int t) { for (; x <= tim; ub(x))tr[x] += t; }int query(int x) {int res = 0;for (; x > 0; db(x))res += tr[x];return res;}
}bt;void dfs(int x) {l[x] = ++tim;for (int j : fail[x])dfs(j);r[x] = tim;
}//字符转数字
int get(string& t) {int x = 0;for (int i = 1; i < sz(t); i++)x = x * 10 + t[i] - '0';return x;
}void solve() {cin >> n >> ki;string t;for (int i = 1; i <= ki; i++) {cin >> t;id[i] = insert(t);}build();dfs(0);//建fail树,跑dfn序//对于每一个有贡献的结点,可以让自身fail子树内所有点获得 1 匹配for (int i = 1; i <= ki; i++) {//区间增加 1bt.modify(l[id[i]], 1), bt.modify(r[id[i]] + 1, -1);}for (int i = 1; i <= n; i++) {cin >> t;if (t[0] == '?') {ll ans = 0;int p = 0;for (int j = 1; j < sz(t); j++) {int u = t[j] - 'a';p = tr[p][u];ans += bt.query(l[p]);//每个位置查询fail路径上有多少贡献}cout << ans << endl;}else if (t[0] == '-') {int u = get(t);if (!st[u]) { //注意判重,不多删多增st[u] = true;//注意映射,bug —— l[u] , u 编号串对应结点是 id[u]bt.modify(l[id[u]], -1), bt.modify(r[id[u]] + 1, 1);}}else {int u = get(t);if (st[u]) {st[u] = false;bt.modify(l[id[u]], 1), bt.modify(r[id[u]] + 1, -1);}}}
}signed main() {cinios;int T = 1;for (int t = 1; t <= T; t++) {solve();}return 0;
}
/*
*/

to be continue…

AC自动机笔记与例题整理相关推荐

  1. 字典树哇 AC自动机哇 = _ =

    字典树哇 AC自动机哇 = _ = 例题 HDU 1251 统计难题 解题思路 : 字典树 原理:按照每个根向下发散 形成一棵 树 这个题 需要在每一个字母处都做统计 (求前缀单词) 开一个 二维数组 ...

  2. 字符串-AC自动机(详细图解)

    文章目录 AC自动机 原理 模板 例题 HDU-2222Keywords Search HDU-2896病毒侵袭 HDU-3065病毒侵袭持续中 POJ-2778DNA Sequence HDU-22 ...

  3. AC自动机:例题与机制详解

    介绍 AC自动机是kmp算法和trie树的结合 大体就是做这样的题用: 可以发现,这题和trie树的区别是把多个单词往一篇文章匹配,而trie恰好相反 匹配的时候其实就是判断子串,所以又用到了kmp ...

  4. 【AC自动机】【字符串】【字典树】AC自动机 学习笔记

    blog:www.wjyyy.top     AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维.     AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...

  5. 学习笔记:AC自动机

    话说AC自动机有什么用......我想要自动AC机 AC自动机简介:  首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配 ...

  6. 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)

    图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...

  7. 【学习笔记+习题集】字符相关(输入输出流,字典树,AC自动机,后缀自动机)(4598字)(更新至2022.12.28)

    目录 板块零:输入输出流 情况一:读取字符串和读取行混用的时候 情况二:关于识别空行 第一题:hdoj2072 情况三:用char数组接受getline函数的输入流 情况四:关于汉字 补充练习: 第一 ...

  8. 提高篇 第二部分 字符串算法 第4章 AC自动机

    https://blog.csdn.net/wangyh1008/article/details/81428056 [模板]AC自动机(加强版) 洛谷3796 AC自动机_A_loud_name-CS ...

  9. AC自动机算法及模板

    AC自动机算法及模板 2016-05-08 18:58 226人阅读 评论(0) 收藏 举报  分类: AC自动机(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 关于 ...

最新文章

  1. jenkins运行日志时间与linux,Jenkins 用户文档(运行多个步骤)
  2. 平滑线反锯齿工具_PS大神常用选框类工具有哪些?其实很简单,小白认真学也能懂...
  3. windows编写linux脚本,Windows PowerShell:共享您的脚本 - 在脚本中编写 Cmdlet | Microsoft Docs...
  4. Echarts地图添加自定义图标
  5. 一维信号双边滤波器_定义图上的各向异性、动态、频谱和多尺度滤波器
  6. P6365 众数出现的次数(水)
  7. Codeforces Round #719 (A-C)
  8. python 和C语言 中的一些容易混淆的符号整理
  9. 转帖:DotNet 资源大全中文版
  10. TCP/IP——链路层简记
  11. python迭代器学习与简单的实践
  12. PHP ASCII 排序方法
  13. Word公式编辑器的使用方法
  14. 马斯克航天新壮举:用1分38秒炸毁火箭,实现载人舱逃逸
  15. 如何将Ant Design Icon本地化
  16. 软件测试用例设计八大方法
  17. Java 求阴历(C++ 求阴历方法的转换)
  18. vue页面无操作30分钟退出登录
  19. 如何从 OVF 或 OVA 文件中部署虚拟机
  20. HTML5中引入字体样式的常用方法-Iconfont(阿里巴巴矢量图库)和IcoMoon-APP

热门文章

  1. https详解+密钥交换算法+公钥与私钥
  2. 中小型企业创业的福音
  3. 3 、库存是企业的墓场
  4. 在Pygtk和Glade使用Gtkbuilder
  5. 第39级台阶 每一步只能迈上1个或2个台阶.先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步.那么,上完39级台阶,有多少种不同的上法(java代码)
  6. mac idea切换多个项目窗口快捷键 command + shift + `
  7. 房地产税预期影响房价走势:一线城市继续上行
  8. 自动化运维工具——【ansible】——从菜鸟到菜鸟
  9. iOS,不能使用UDID之后
  10. Unreal Open Day游记