AC 自动机(ˉ﹃ˉ)

其实学这么久字符串就是冲着这个算法在学,终于学到了

一、模板

1、【模板】AC自动机(简单版)

题目背景

警告:通过套取数据而直接“打表”过题者,是作弊行为,发现即棕名。

这是一道简单的 AC 自动机模板题,用于检测正确性以及算法常数。

题目描述

给定 n 个模式串 si 和一个文本串 t,求有多少个不同的模式串在文本串里出现过。
两个模式串不同当且仅当他们编号不同。

输入格式

第一行是一个整数,表示模式串的个数 n
第 2 到第 (n+1) 行,每行一个字符串,第 (i+1) 行的字符串表示编号为 i 的模式串 si​。
最后一行是一个字符串,表示文本串 t

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1

3
a
aa
aa
aaa

输出 #1

3

输入 #2

4
a
ab
ac
abc
abcd

输出 #2

3

输入 #3

2
a
aa
aa

输出 #3

2

说明/提示

样例 1 解释

s2 与 s3 编号(下标)不同,因此各自对答案产生了一次贡献。

样例 2 解释

s1,s2,s4 都在串 abcd 里出现过。

数据规模与约定

  • 对于 50% 的数据,保证 n=1。
  • 对于 100% 的数据,保证

先用trie树把数据存起来,然后构造fail指针。就有点类似kmp的感觉,构造之后就能不从头开始匹配,匹配到不能匹配之后直接到下一个去掉开头一段的点继续匹配(相当于缩短链)构造之后输入长串开始匹配,匹配的时候就是在对trie树遍历,只不过现在搜到头之后,可以找到一个后缀串继续匹配,就快很多。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
struct Tree{//失配指针int fail;//子节点的位置int vis[26];//标记有几个单词以这个节点结尾int ed = 0;
}AC[1000000];
int cnt = 0;
inline void Build(string s){int l = s.size();//字典树的当前指针int now = 0;//构造trie树for(int i = 0; i < l; i++){int c = s[i] - 'a';if(AC[now].vis[c] == 0){AC[now].vis[c] = ++cnt;}now = AC[now].vis[c];}AC[now].ed += 1;
}
//构造fail指针
void Get_fail(){queue <int> q;//提前处理第二层for(int i = 0; i < 26; i++){//只要存在就指向根节点if(AC[0].vis[i] != 0){AC[AC[0].vis[i]].fail = 0;//指向根节点q.push(AC[0].vis[i]);//压入队列}}while(!q.empty()){int u = q.front();q.pop();//从第三层开始逐层枚举所有子节点for(int i = 0; i < 26; i++){if(AC[u].vis[i] != 0){AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];//子节点的fail指针指向当前节点的//fail指针所指向的节点的相同子节点//关于这个地方为什么指向该节点:这个地方要用KMP的思想来理解//其实就是把相同的那一段跳过了q.push(AC[u].vis[i]);//压入队列}else{//如果这个节点不存在,当前节点的子节点就指向前节点fail指针指向的结点AC[u].vis[i]=AC[AC[u].fail].vis[i];}}}
}
int query(string s){int l = s.size();int now = 0, ans = 0;for(int i = 0; i < l; i++){now = AC[now].vis[s[i]-'a'];//向下一层for(int t = now; t && AC[t].ed != -1; t = AC[t].fail)//循环求解{//这个地方看了一些题解,感觉理解更好了一些//其实就是,如果能匹配,就继续延长链,如果失败就缩短链ans += AC[t].ed;AC[t].ed = -1;}}return ans;
}
int main()
{ios::sync_with_stdio(false);int n;string s;cin >> n;for(int i = 1; i <= n; i++){cin >> s;Build(s);}AC[0].fail = 0;Get_fail();cin >> s;cout << query(s) <<endl;return 0;
}

2、【模板】AC自动机(加强版)

题目描述

N 个由小写字母组成的模式串以及一个文本串 T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串 T 中出现的次数最多。

输入格式

输入含多组数据。保证输入数据不超过 50 组。

每组数据的第一行为一个正整数 N,表示共有 N 个模式串,1≤N≤150。

接下去 N 行,每行一个长度小于等于 70 的模式串。下一行是一个长度小于等于 10^6 的文本串 T。保证不存在两个相同的模式串。

输入结束标志为 N=0。

输出格式

对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。

输入输出样例

输入 #1

2
aba
bab
ababababac
6
beta
alpha
haha
delta
dede
tata
dedeltalphahahahototatalpha
0

输出 #1

4
aba
2
alpha
haha

统计匹配的次数即可

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 1e6 + 5;struct tree{int vis[26], fail, pos;
}AC[N];struct Ans{int pos, num;
}ans[N];
int tot = 0;
void clean(int pos){memset(AC[pos].vis, 0, sizeof(AC[pos].vis));AC[pos].fail = 0;AC[pos].pos = 0;
}
bool cmp(Ans a, Ans b){if(a.num == b.num){return a.pos < b.pos;}return a.num > b.num;
}
void add(string s, int num){int pos = 0;int len = s.size();for(int i = 0; i < len; i++){int c = s[i] - 'a';if(!AC[pos].vis[c]){AC[pos].vis[c] = ++tot;clean(tot);}pos = AC[pos].vis[c];}AC[pos].pos = num;
}
void getFail(){queue<int> q;for(int i = 0; i < 26; i++){if(AC[0].vis[i]){AC[AC[0].vis[i]].fail = 0;q.push(AC[0].vis[i]);}}while(!q.empty()){int now = q.front();q.pop();for(int i = 0; i < 26; i++){if(AC[now].vis[i]){AC[AC[now].vis[i]].fail = AC[AC[now].fail].vis[i];q.push(AC[now].vis[i]);}else{AC[now].vis[i] = AC[AC[now].fail].vis[i];}}}
}
void query(string s){int l = s.length();int now = 0;for(int i = 0; i < l; i++){now = AC[now].vis[s[i] - 'a'];for(int j = now; j; j = AC[j].fail){ans[AC[j].pos].num++;}}
}
string s[200];
int main()
{ios::sync_with_stdio(false);for(;;){int n;cin >> n;if(n == 0){break;}tot = 0;clean(0);for(int i = 1; i <= n; i++){cin >> s[i];ans[i].num = 0;ans[i].pos = i;add(s[i], i);}AC[0].fail = 0;getFail();cin >> s[0];query(s[0]);sort(ans + 1, ans + 1 + n, cmp);cout<<ans[1].num<<endl;cout<<s[ans[1].pos]<<endl;for(int i = 2; i <= n; i++){if(ans[i].num == ans[i-1].num){cout<<s[ans[i].pos]<<endl;}else{break;}}}return 0;
}

3、【模板】AC自动机(二次加强版)

题目描述

给你一个文本串 Sn 个模式串 T1n,请你分别求出每个模式串 TiS 中出现的次数。

输入格式

第一行包含一个正整数 n 表示模式串的个数。

接下来 n 行,第 i 行包含一个由小写英文字母构成的字符串 Ti

最后一行包含一个由小写英文字母构成的字符串 S

数据不保证任意两个模式串不相同

输出格式

输出包含 n 行,其中第 i 行包含一个非负整数表示 TiS 中出现的次数。

输入输出样例

输入 #1

5
a
bb
aa
abaa
abaaa
abaaabaa

输出 #1

6
0
3
2
1

说明/提示

1≤n≤2×10^5,1…n 的长度总和不超过 2×10^5,S 的长度不超过 2×10^6。

这个题看了大佬的题解之后对之前的模板做了一些优化,感觉也更好懂了。首先还是建trie树,存每个字符串的结尾位置。然后构建fail,构建的是每个位置的fail组成的数组。然后连起来变成fail图。然后遍历文本串,把每个位置出现的次数统计一下。然后深搜,把答案加起来。最后输出

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 2e5 + 3;
int n, tot = 1, cnt = 0;
int tr[N][30], ed[N], fail[N], cn[N], nxt[N], to[N], head[N];
void add(string s, int id){int pos = 1;for(int i = 0; s[i]; i++){int c = s[i] - 'a';if(!tr[pos][c]){tr[pos][c] = ++tot;}pos = tr[pos][c];}ed[id] = pos;
}
void edge(int u, int v){nxt[++cnt] = head[u];head[u] = cnt;to[cnt] = v;
}
void getFail(){for(int i = 0; i < 26; i++){tr[0][i] = 1;}queue<int> q;q.push(1);while(!q.empty()){int pos = q.front();q.pop();for(int i = 0; i < 26; i++){if(tr[pos][i]){fail[tr[pos][i]] = tr[fail[pos]][i];q.push(tr[pos][i]);}else{tr[pos][i] = tr[fail[pos]][i];}}}
}
void dfs(int pos){for(int i = head[pos]; i; i = nxt[i]){int v = to[i];dfs(v);cn[pos] += cn[v];}
}
int main()
{ios::sync_with_stdio(false);cin >> n;string s;for(int i = 1; i <= n; i++){cin >> s;add(s, i);}getFail();cin >> s;int pos = 1;for(int i = 0; s[i]; i++){pos = tr[pos][s[i] - 'a'];cn[pos]++;}//建 fail 树for(int i = 2; i <= tot; i++){edge(fail[i], i);}dfs(1);for(int i = 1; i <= n; i++){cout<<cn[ed[i]]<<endl;}return 0;
}

二、练习

1、[USACO15FEB]Censoring G

题目描述

FJ 把杂志上所有的文章摘抄了下来并把它变成了一个长度不超过 10^5 的字符串 s。他有一个包含 n 个单词的列表,列表里的 n 个单词记为 t1⋯tn。他希望从 s 中删除这些单词。

FJ 每次在 s 中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从 s 中删除这个单词。他重复这个操作直到 s 中没有列表里的单词为止。注意删除一个单词后可能会导致 s 中出现另一个列表中的单词。

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

请帮助 FJ 完成这些操作并输出最后的 s

输入格式

第一行是一个字符串,表示文章 s

第二行有一个整数,表示单词列表的单词个数 n

第 3 到第 (n+2) 行,每行一个字符串,第 (i+2) 行的字符串 ti 表示第 i 个单词。

输出格式

输出一行一个字符串,表示操作结束后的 s

输入输出样例

输入 #1

begintheescapexecutionatthebreakofdawn
2
escape
execution

输出 #1

beginthatthebreakofdawn

说明/提示

数据规模与约定

对于全部的测试点,保证:

  • 字符串均只含小写字母。
  • 操作结束后 s 不会被删成空串。
  • 对于所有的 i\=jti 不是 tj 的子串。

其中对于一个字符串 x,约定 ∣x∣ 表示 x 的长度。


提示

操作过程中 s 有可能某一个前缀子串被完全删除,请格外注意这一点。

建AC自动机,这回用了暴力跳边,之前题解说这样写有问题,但也没搞明白怎么用fail图暂时用暴力写的。就是匹配,遇到结尾就删除,用栈模拟删除即可。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 1e5 +3;
int trie[N][30];
int ed[N], fail[N], to[N], nxt[N], head[N], po[N], hd[N], top;
int tot = 0, cnt = 0;
void add(string s){int len = s.size();int pos = 0;for(int i = 0; i < len; i++){int c = s[i] - 'a';if(!trie[pos][c]){trie[pos][c] = ++tot;}pos = trie[pos][c];}ed[pos] = len;
}
string ss;
void getFail(){queue<int> q;for(int i = 0; i < 26; i++){if(trie[0][i]){q.push(trie[0][i]);}}while(!q.empty()){int pos = q.front();q.pop();for(int i  = 0; i < 26; i++){if(trie[pos][i]){fail[trie[pos][i]] = trie[fail[pos]][i];q.push(trie[pos][i]);}else{trie[pos][i] = trie[fail[pos]][i];}}}
}
void solve(string s){int pos = 0;for(int i = 0; s[i]; i++){int c = s[i] - 'a';pos = trie[pos][c];po[++top] = pos;hd[top] = i;if(ed[pos]){top -= ed[pos];if(!top){pos = 0;}else{pos = po[top];}}}
}int main()
{ios::sync_with_stdio(false);cin >> ss;int n;cin >> n;for(int i = 1; i <= n; i++){string s;cin >> s;add(s);}getFail();solve(ss);for(int i = 1; i <= top; i++){cout<<ss[hd[i]];}return 0;
}

2、[POI2000]病毒

题目描述

二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。

示例:

例如如果 {011,11,00000} 为病毒代码段,那么一个可能的无限长安全代码就是 010101…。如果 {01,11,000000} 为病毒代码段,那么就不存在一个无限长的安全代码。

现在给出所有的病毒代码段,判断是否存在无限长的安全代码。

输入格式

第一行包括一个整数 n,表示病毒代码段的数目。

以下的 n 行每一行都包括一个非空的 01 字符串,代表一个病毒代码段。

输出格式

如果存在无限长的安全代码,输出 TAK,否则输出 NIE

输入输出样例

输入 #1

3
01
11
00000

输出 #1

NIE

说明/提示

1≤n≤2000,所有病毒代码段的总长度不超过 3×10^4。

其实之前对于无限长的问题一直不知道怎么处理,现在才知道要判断成环。当我们构造fail数组之后,就是把trie树变成了一个图,现在就是要在这个图里面找环,前提是不能接触到结尾,否则会匹配到模式串。然后还有后缀的情况,这种情况就是在构建fail数组的时候,失配节点指向模式串尾的,把自己也标记了就行了。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 3e4 + 5;
int tr[N][2];
int ed[N], fail[N];
bool sign[N], vis[N];
int tot = 0;
void add(string s){int pos = 0;for(int i = 0; s[i]; i++){int c = s[i] - '0';if(!tr[pos][c]){tr[pos][c] = ++tot;}pos = tr[pos][c];}ed[pos] = 1;
}
void getFail(){queue<int> q;for(int i = 0; i <= 1; i++){if(tr[0][i]){q.push(tr[0][i]);}}while(!q.empty()){int pos = q.front();q.pop();for(int i = 0; i < 2; i++){if(tr[pos][i]){fail[tr[pos][i]] = tr[fail[pos]][i];ed[tr[pos][i]]|=ed[fail[tr[pos][i]]];q.push(tr[pos][i]);}else{tr[pos][i] = tr[fail[pos]][i];}}}
}
bool tag = 0;
void solve(int x){if(tag){return;}if(sign[x]){cout<<"TAK"<<endl;tag = true;return;}if(vis[x] || ed[x] != 0){return;}vis[x] = true;sign[x] = true;solve(tr[x][0]);solve(tr[x][1]);sign[x] = false;
}
int main()
{ios::sync_with_stdio(false);int n;cin >> n;for(int i = 1; i <= n; i++){string s;cin >> s;add(s);}getFail();solve(0);if(!tag){cout<<"NIE"<<endl;}return 0;
}

3、[JSOI2007]文本生成器

题目描述

JSOI 交给队员 ZYX 一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是 GW 文本生成器 v6 版。

该软件可以随机生成一些文章——总是生成一篇长度固定且完全随机的文章。 也就是说,生成的文章中每个字符都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,那么我们说这篇文章是可读的(我们称文章 s 包含单词 t,当且仅当单词 t 是文章 s 的子串)。但是,即使按照这样的标准,使用者现在使用的 GW 文本生成器 v6 版所生成的文章也是几乎完全不可读的。ZYX 需要指出 GW 文本生成器 v6 生成的所有文本中,可读文本的数量,以便能够成功获得 v7 更新版。你能帮助他吗?

答案对 10^4 + 7 取模。

输入格式

第一行有两个整数,分别表示使用者了解的单词总数 n 和生成的文章长度 m

接下来 n 行,每行一个字符串 si,表示一个使用者了解的单词。

输出格式

输出一行一个整数表示答案对 10^4 + 7 取模的结果。

输入输出样例

输入 #1

2 2
A
B

输出 #1

100

说明/提示

数据规模与约定

对于全部的测试点,保证:

  • 1≤n≤60,1≤m≤100。
  • 1≤∣si∣≤100,其中 ∣si∣ 表示字符串 si 的长度。
  • si 中只含大写英文字母。

AC自动机 + DP和上一道题的构造方式有点像,存住所有结尾,dp所有非ed数组的结点,然后用总数减去即可

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 1e4 + 7;
int trie[N][30];
int n, m, tot = 0;
int ed[N], fail[N];
void add(string s){int pos = 0;for(int i = 0; s[i]; i++){int c = s[i] - 'A';if(!trie[pos][c]){trie[pos][c] = ++tot;}pos = trie[pos][c];}ed[pos] = 1;
}
void getFail(){queue<int> q;for(int i = 0; i < 26; i++){if(trie[0][i]){q.push(trie[0][i]);}}while(!q.empty()){int pos = q.front();q.pop();for(int i = 0; i < 26; i++){if(trie[pos][i]){fail[trie[pos][i]] = trie[fail[pos]][i];ed[trie[pos][i]] |= ed[trie[fail[pos]][i]];q.push(trie[pos][i]);}else{trie[pos][i] = trie[fail[pos]][i];}}}
}
int dp[105][N];
int ans = 0, res = 1;
int main()
{ios::sync_with_stdio(false);cin >> n>> m;for(int i = 1; i <= n; i++){string s;cin >> s;add(s);}getFail();dp[0][0] = 1;for(int i = 1; i <= m; i++){for(int j = 0; j <= tot; j++){for(int k = 0; k < 26; k++){if(!ed[trie[j][k]]){(dp[i][trie[j][k]] += dp[i-1][j]) %= N;}}}}for(int i = 0; i <= tot; i++){(ans += dp[m][i]) %= N;}for(int i = 1; i <= m; i++){res =  res * 26 % N;}cout<<((res - ans + N) % N);return 0;
}

4、[SDOI2014] 数数

题目描述

我们称一个正整数 x 是幸运数,当且仅当它的十进制表示中不包含数字串集合 s 中任意一个元素作为其子串。例如当 s = {22, 333, 0233}时,233 是幸运数,2333、20233、3223 不是幸运数。给定 ns,计算不大于 n 的幸运数个数。

答案对 10^9 + 7 取模。

输入格式

第一行有一个整数,表示 n

第二行有一个整数,表示 s 中的元素个数 m

接下来 m 行,每行一个数字串 si,表示 s 中的一个元素。

输出格式

输出一行一个整数,表示答案对 10^9 + 7 取模的结果。

输入输出样例

输入 #1

20
3
2
3
14

输出 #1

14

说明/提示

样例 1 解释

除了 3,13,2,12,20,14 以外,不大于 20 的整数都是幸运数字。

数据规模与约定

对于全部的测试点,保证:

和上一个有点像只不过上一个是要所有带带结尾的,现在是要不带结尾的,而且要比n小,这个地方的处理就很麻烦,dp的时候要分很多情况,如果现在已经和n分出大小了,就可以乱加了,如果还没分出大小,就要贴着加。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 1210;
const int M = 1510;
const int mod = 1e9 + 7;
int tr[M][10];
int fail[M];
bool ed[M];
char n[N];
int m, len, tot = 1, ans;
void add(string s){int pos = 1;int l = s.size();for(int i = 0 ; i < l; i++){int c = s[i] - '0';if(!tr[pos][c]){tr[pos][c] = ++tot;}pos = tr[pos][c];}ed[pos] = 1;
}
void getFail(){queue<int> q;for(int i = 0; i <= 9; i++){tr[0][i] = 1;}while(q.size()){q.pop();}q.push(1);while(!q.empty()){int pos = q.front();q.pop();ed[pos] |= ed[fail[pos]];for(int i = 0; i <= 9; i++){if(tr[pos][i]){fail[tr[pos][i]] = tr[fail[pos]][i];q.push(tr[pos][i]);}else{tr[pos][i] = tr[fail[pos]][i];}}}
}
int dp[2][M][2];
int main()
{ios::sync_with_stdio(false);cin >> n + 1;cin >> m;len = strlen(n + 1);for(int i = 1; i <= m; i++){string s;cin >> s;add(s);}getFail();for(int i=1;i<=n[1]-'0';i++){if(!ed[tr[1][i]]){dp[1][tr[1][i]][i == n[1] - '0'] += 1;}}for(int i=2;i<=len;i++){memset(dp[i&1], 0, sizeof(dp[i&1]));for(int j = 1; j <= 9; j++){if(!ed[tr[1][j]]){dp[i&1][tr[1][j]][0] += 1;}}for(int j = 1; j <= tot; j++){if(ed[j]){continue;}if(dp[(i - 1) & 1][j][0]){for(int c = 0; c <= 9; c++){if(!ed[tr[j][c]]){dp[i&1][tr[j][c]][0] = (dp[i&1][tr[j][c]][0] + dp[(i-1)&1][j][0]) % mod;}}}if(dp[(i - 1) & 1][j][1]){for(int c = 0; c <= n[i] - '0'; c++){if(!ed[tr[j][c]]){dp[i&1][tr[j][c]][c == n[i] - '0'] = (dp[i&1][tr[j][c]][c == n[i] - '0'] + dp[(i-1)&1][j][1]) % mod;}}}}}for(int i=1;i<=tot;i++){if(!ed[i]){ans = (ans + dp[len & 1][i][0]) % mod;ans = (ans + dp[len & 1][i][1]) % mod;}}cout<<ans<<endl;return 0;
}

AC 自动机(ˉ﹃ˉ)相关推荐

  1. Aho-Corasick 多模式匹配算法(AC自动机) 的算法详解及具体实现

    多模式匹配 多模式匹配就是有多个模式串P1,P2,P3-,Pm,求出所有这些模式串在连续文本T1-.n中的所有可能出现的位置. 例如:求出模式集合{"nihao","ha ...

  2. 【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

    [BZOJ2434][NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P ...

  3. AC自动机算法及模板

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

  4. Keywords Search AC自动机QAQ

    AC自动机,一直以来都以为是一个非常高大上的算法,其实它还真的挺高大上的. 首先来说,ac自动机的思想与kmp类似,需要自己模拟来理解. 给两个博客: http://www.cppblog.com/m ...

  5. 字符串匹配算法 -- AC自动机 基于Trie树的高效的敏感词过滤算法

    文章目录 1. 算法背景 2. AC自动机实现原理 2.1 构建失败指针 2.2 依赖失败指针过滤敏感词 3. 复杂度及完整代码 1. 算法背景 之前介绍过单模式串匹配的高效算法:BM和KMP 以及 ...

  6. 【Luogu3041】视频游戏的连击(AC自动机,动态规划)

    题面链接 题解 首先构建出AC自动机 然后在AC自动机上面跑DP 转移很显然从Trie树的节点跳到他的儿子节点 但是要注意一个问题, 在计算的时候,每一个节点加入后能够 造成的贡献 要加上他的子串的贡 ...

  7. HDU2896(AC自动机模版题)

    AC自动机模版题: 方法一:超时 #include<iostream> #include<algorithm> #include<cstring> #include ...

  8. HDU2222(AC自动机模版题)

    AC自动机是Trie树和KMP的结合物,但是其实KMP在这里体现了思想,而Trie树才是最重要的,要想学懂AC自动机,学习Trie树是必须的,这些是自己在学习AC自动机的个人看法,我也是在网上学习了大 ...

  9. AC自动机 + 概率dp + 高斯消元 --- HDU 5955 or 2016年沈阳icpc H [AC自动机 + 概率dp + 高斯消元]详解

    题目链接 题目大意: 就是有NNN个人,每个人都会猜一个长度为LLL的只包含{1,2,3,4,5,6}\{1,2,3,4,5,6\}{1,2,3,4,5,6}的序列,现在裁判开始投掷骰子,并且把每次的 ...

  10. P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)

    P5357 [模板]AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数) 传送门 形式上,AC 自动机基于由若干模式串构成的 Trie 树,并在此之上增加了一些 fail 边:本质 ...

最新文章

  1. Docker Swarm集群搭建
  2. 数据结构【高精度专题】
  3. 如何通过浏览器在所有响应内容中查找文本
  4. Photoshop使用的八招密技
  5. 转 Oracle DBCA高级玩法:从模板选择、脚本调用到多租户
  6. I.MX6 Linux kernel LVDS backlight enable
  7. Ant运行build.xml执行服务器scp,异常解决jsch.jar
  8. 加密 CryptoJS DES
  9. docker本地仓库(本地镜像仓库)环境搭建
  10. php 单词替换,单词替换 - Shiyin's note
  11. 中国名人书画展由世界全媒体联盟中国区及广西明星影视文化传媒有限公司联合举办
  12. JSP 实用程序之简易图片服务器
  13. JavaScript 每日一题 #10
  14. 使用python manage.py startapp myapp未报错,但是没有创建出myapp
  15. 十二星座匹配对象_水瓶座最佳配对对象是谁
  16. 向六种植物学习创业的真谛
  17. 提升精度 | 新的小样本学习算法提升物体识别精度(附论文地址)
  18. 易启秀20150629完整包微场景制作源码,新增1.4G素材包,全新后台UI设计+采集
  19. 个人信息管理系统数据表设计
  20. iOS APP图标一键生成

热门文章

  1. BL24Cxx系列EEPROM测试总结
  2. 牛客网剑指offer——Java题解
  3. oracle 自动sga,Oracle实用脚本 : 收集自动SGA内存管理ASMM诊断信息
  4. 20161214关于superfetch导致内在占用高
  5. 优课联盟新视野英语(6-10)
  6. JavaScript中实现首字母大写,小写
  7. ubuntu14.04 keras安装以及Pycharm配置详解
  8. ARM架构与x86架构的区别
  9. 运行python脚本后台执行
  10. RabbitMQ之Shovel配置(python模拟Producer、Consumer)