AC自动机笔记与例题整理
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)
洛谷:文本生成器(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的字符串匹配方案数)
所以我们正向模式串和反向模式串分别建立正反自动机,最后在顺序遍历文本串记录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自动机笔记与例题整理相关推荐
- 字典树哇 AC自动机哇 = _ =
字典树哇 AC自动机哇 = _ = 例题 HDU 1251 统计难题 解题思路 : 字典树 原理:按照每个根向下发散 形成一棵 树 这个题 需要在每一个字母处都做统计 (求前缀单词) 开一个 二维数组 ...
- 字符串-AC自动机(详细图解)
文章目录 AC自动机 原理 模板 例题 HDU-2222Keywords Search HDU-2896病毒侵袭 HDU-3065病毒侵袭持续中 POJ-2778DNA Sequence HDU-22 ...
- AC自动机:例题与机制详解
介绍 AC自动机是kmp算法和trie树的结合 大体就是做这样的题用: 可以发现,这题和trie树的区别是把多个单词往一篇文章匹配,而trie恰好相反 匹配的时候其实就是判断子串,所以又用到了kmp ...
- 【AC自动机】【字符串】【字典树】AC自动机 学习笔记
blog:www.wjyyy.top AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维. AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...
- 学习笔记:AC自动机
话说AC自动机有什么用......我想要自动AC机 AC自动机简介: 首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配 ...
- 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)
图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...
- 【学习笔记+习题集】字符相关(输入输出流,字典树,AC自动机,后缀自动机)(4598字)(更新至2022.12.28)
目录 板块零:输入输出流 情况一:读取字符串和读取行混用的时候 情况二:关于识别空行 第一题:hdoj2072 情况三:用char数组接受getline函数的输入流 情况四:关于汉字 补充练习: 第一 ...
- 提高篇 第二部分 字符串算法 第4章 AC自动机
https://blog.csdn.net/wangyh1008/article/details/81428056 [模板]AC自动机(加强版) 洛谷3796 AC自动机_A_loud_name-CS ...
- AC自动机算法及模板
AC自动机算法及模板 2016-05-08 18:58 226人阅读 评论(0) 收藏 举报 分类: AC自动机(1) 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 关于 ...
最新文章
- jenkins运行日志时间与linux,Jenkins 用户文档(运行多个步骤)
- 平滑线反锯齿工具_PS大神常用选框类工具有哪些?其实很简单,小白认真学也能懂...
- windows编写linux脚本,Windows PowerShell:共享您的脚本 - 在脚本中编写 Cmdlet | Microsoft Docs...
- Echarts地图添加自定义图标
- 一维信号双边滤波器_定义图上的各向异性、动态、频谱和多尺度滤波器
- P6365 众数出现的次数(水)
- Codeforces Round #719 (A-C)
- python 和C语言 中的一些容易混淆的符号整理
- 转帖:DotNet 资源大全中文版
- TCP/IP——链路层简记
- python迭代器学习与简单的实践
- PHP ASCII 排序方法
- Word公式编辑器的使用方法
- 马斯克航天新壮举:用1分38秒炸毁火箭,实现载人舱逃逸
- 如何将Ant Design Icon本地化
- 软件测试用例设计八大方法
- Java 求阴历(C++ 求阴历方法的转换)
- vue页面无操作30分钟退出登录
- 如何从 OVF 或 OVA 文件中部署虚拟机
- HTML5中引入字体样式的常用方法-Iconfont(阿里巴巴矢量图库)和IcoMoon-APP
热门文章
- https详解+密钥交换算法+公钥与私钥
- 中小型企业创业的福音
- 3 、库存是企业的墓场
- 在Pygtk和Glade使用Gtkbuilder
- 第39级台阶 每一步只能迈上1个或2个台阶.先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步.那么,上完39级台阶,有多少种不同的上法(java代码)
- mac idea切换多个项目窗口快捷键 command + shift + `
- 房地产税预期影响房价走势:一线城市继续上行
- 自动化运维工具——【ansible】——从菜鸟到菜鸟
- iOS,不能使用UDID之后
- Unreal Open Day游记