ac自动机 也不知道 学过多少遍了。
它具体干的事情就是在一棵trie树上跑kmp。
我们先来想一想普通的kmp在做些什么:
fail数组建立时:
维护一个j值, 表示当前点的上一个节点最多可以匹配到匹配串的哪里, 然后一直往前推j (令j = next[j]) , 直到j + 1 == 当前点。
匹配时也差不多。
在trie树上fail指针想要实现的内容是和kmp一样的, 但是考虑到它们在结构上的一定 区别和共性
在一个子串中, fail[i] 与 i 的关系是: fail[i] 的位置一定在 i 的前面
而在trie树上, fail[i] 与 i 的关系是 : fail[i] 的深度 一定小于 i。
好了其它的地方就都一样了。
所以唯一不一样的地方就是要保证访问的顺序, 这可以用bfs 简单实现。
这时候ac自动机就基本等同于kmp了。
在代码实现上有一个小技巧, 就是因为ac自动机 表示子串时大多用应用了指针思想的trie树, 所以每一次找第一个 j + 1 == i 的那个j 时并不用真的像kmp那样while上去, 而是可以直接在一个 节点p 没有q这个后继时 将p 的q 后继指向 fail[p]的 q 后继。 考虑到 fail[p] 的深度比 p 小(即它的后继已经如此处理过了), 所以 p 这时指向的就是其 所有fail 上去的节点中 拥有 q 后继的深度最大的。
代码写起来非常简单优美, 只有60多行, 感人肺腑。
上两道裸题:
hdu 2222
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define MAXN 510005
using namespace std;
char s[MAXN * 2];
int root, cnt, next[MAXN][26], fail[MAXN], end[MAXN];
int newnode(){
for(int i = 0; i < 26; i ++)next[cnt][i] = -1;
end[cnt ++] = 0;
return cnt - 1;
}
void insert(char s[]){
int len = strlen(s), now = root;
for(int i = 0; i < len; i ++){
if(next[now][s[i] - 'a'] == -1)next[now][s[i] - 'a'] = newnode();
now = next[now][s[i] - 'a'];
}end[now] ++;
}
void build(){
queue<int>q;
fail[root] = root;
for(int i = 0; i < 26; i ++)
if(next[root][i] == -1)next[root][i] = root;
else{
fail[next[root][i]] = root;
q.push(next[root][i]);
}
while( ! q.empty()){
int now = q.front(); q.pop();
for(int i = 0; i < 26; i ++)
if(next[now][i] == -1)next[now][i] = next[fail[now]][i];
else{
fail[next[now][i]] = next[fail[now]][i];
q.push(next[now][i]);
}
}
}
int query(char s[]){
int len = strlen(s), now = root, res = 0;
for(int i = 0; i < len; i ++){
now = next[now][s[i] - 'a'];
int t = now;
while(t != root){
res += end[t];
end[t] = 0;
t = fail[t];
}
}return res;
}
int main(){
int t; scanf("%d", &t); while(t --){
cnt = 0; root = newnode();
int n; scanf("%d", &n);while(n --){
scanf("%s", s);insert(s);
}
build();
scanf("%s", s);
printf("%d\n", query(s));
}
return 0;
}
hdu 2896
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define MAXN 100005
using namespace std;
int n, m, root, cnt, next[MAXN][128], fail[MAXN], end[MAXN], summ, ans[5];
bool exist[508];
char s[10005];
int newnode(){
for(int i = 0; i < 128; i ++)next[cnt][i] = -1;
end[cnt ++] = -1; return cnt - 1;
}
void insert(char s[], int ind){
int now = root, len = strlen(s);
for(int i = 0; i < len; i ++){
if(next[now][s[i]] == -1)next[now][s[i]] = newnode();
now = next[now][s[i]];
} end[now] = ind;
}
void makefail(){
queue<int>q;
fail[root] = root;
for(int i = 0; i < 128; i ++)
if(next[root][i] == -1)next[root][i] = root;
else fail[next[root][i]] = root, q.push(next[root][i]);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i < 128; i ++)
if(next[u][i] == -1)next[u][i] = next[fail[u]][i];
else fail[next[u][i]] = next[fail[u]][i], q.push(next[u][i]);
}
}
void query(char s[]){
int now = root, len = strlen(s);
memset(exist, 0, sizeof(exist));
for(int i = 0; i < len; i ++){
now = next[now][s[i]];
int t = now;
while(t != root){
if(end[t] != -1 && !exist[end[t]])ans[++ ans[0]] = end[t], exist[end[t]] = 1;
t = fail[t];
}
}
}
int main(){
scanf("%d", &n);
root = newnode();
for(int i = 1; i <= n; i ++)scanf("%s", s), insert(s, i);
makefail();
scanf("%d", &m);
for(int i = 1; i <= m; i ++){
scanf("%s", s); ans[0] = 0;
query(s);
if(ans[0]){
printf("web %d:", i);
sort(ans + 1, ans + ans[0] + 1);
for(int i = 1; i <= ans[0]; i ++)printf(" %d", ans[i]);puts("");
summ ++;
}
}printf("total: %d\n", summ);
return 0;
}
hdu 3065 同裸题, 练手
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define MAXL 2000005
#define MAXN 250005
using namespace std;
int root, n, next[MAXN][26], fail[MAXN], cnt, end[MAXN][20], ans[1005];
char s[MAXL], ch[1005][50];
queue<int>q;
int newnode(){
memset(next[cnt], -1, sizeof(next[cnt]));
end[cnt][0] = 0; cnt ++;
return cnt - 1;
}
void insert(char s[], int ind){
int len = strlen(s), now = root;
for(int i = 0; i < len; i ++){
if(next[now][s[i] - 'A'] == -1)next[now][s[i] - 'A'] = newnode();
now = next[now][s[i] - 'A'];
}end[now][++ end[now][0]] = ind;
}
void build(){
for(int i = 0; i < 26; i ++)
if(next[root][i] == -1)next[root][i] = root;
else fail[next[root][i]] = root, q.push(next[root][i]);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i < 26; i ++)
if(next[u][i] == -1)next[u][i] = next[fail[u]][i];
else fail[next[u][i]] = next[fail[u]][i], q.push(next[u][i]);
}
}
void query(char s[]){
int len = strlen(s), now = root;
for(int i = 0; i < len; i ++){
if(s[i] < 'A' || s[i] > 'Z'){now = root; continue;}
now = next[now][s[i] - 'A'];
int t = now;
while(t != root){
if(end[t][0]){
for(int j = 1; j <= end[t][0]; j ++)ans[end[t][j]] ++;
}
t = fail[t];
}
}
}
int main(){
while(scanf("%d", &n) != EOF){
cnt = 0;
root = newnode();
memset(ans, 0, sizeof(ans));
scanf("%d", &n);for(int i = 1; i <= n; i ++){
scanf("%s", ch[i]);
insert(ch[i], i);
}
build();
scanf("%s", s);
query(s);
for(int i = 1; i <= n; i ++)
if(ans[i]){
printf("%s: %d\n", ch[i], ans[i]);
}
}
return 0;
}
【ac 自动机上的dp】
ac 自动机 有一个较为灵活的end数组, 在前面出现的练习里, 它有着一些很显然的应用: 记录是否为结束字符, 记录是哪个子串的结束字符等, 但是它可以有一些更好的应用。 比如说 可以把end记录为: 当前这个点的所有后缀是不是有一个是一个字符串的结束字符。 这个显然把 所有它的 fail 指针记录的end 或起来就可以了, 但是可以有一些很好的用途。
如:
bzoj 1030
长 m 的所有字符串中有多少个是包含 任意一个子串的。
考虑不包含的情况, 如果经过了任意一个结束字符它以及它的所有儿子不包含的情况就是0了。
考虑 长为 i 的串, 当前在 点j , 有 f[i][j] 中情况不包含子串, 对于 a ~ z 每一个下一步的可能 k, f[i + 1][next[j][k]都可以加上 f[i][j] 当然前提是j 这个点 没有是结束字符的后缀。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define MAXN 10005
#define mod 10007
using namespace std;
int n, m, cnt, next[MAXN][26], root, end[MAXN], fail[MAXN], f[110][MAXN];
char s[MAXN];
inline int newnode(){
memset(next[cnt], -1, sizeof(next[cnt]));
cnt ++ ; return cnt - 1;
}
void insert(char s[]){
int len = strlen(s), p = root;
for(int i = 0; i < len; i ++){
if(next[p][s[i] - 'A'] == -1)next[p][s[i] - 'A'] = newnode();
p = next[p][s[i] - 'A'];
}end[p] = 1;
}
queue<int>q;
void build(){
fail[root] = root;
for(int i = 0; i < 26; i ++){
if(next[root][i] == -1)next[root][i] = root;
else fail[next[root][i]] = root, q.push(next[root][i]);
}
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i < 26; i ++){
if(next[u][i] == -1)next[u][i] = next[fail[u]][i];
else fail[next[u][i]] = next[fail[u]][i], end[next[u][i]] |= end[next[fail[u]][i]], q.push(next[u][i]);
}
}
}
int main(){
root = newnode();
scanf("%d%d", &n, &m);while(n --){
scanf("%s", s);insert(s);
} build();
f[0][0] = 1;
for(int i = 0; i < m; i ++)
for(int j = 0; j < cnt; j ++)if(f[i][j])
for(int k = 0; k < 26; k ++){
int now = next[j][k];
if(!end[now])
f[i + 1][now] = (f[i + 1][now] + f[i][j]) % mod;
}
int ans = 0;
for(int i = 0; i < cnt; i ++)ans = (ans + f[m][i]) % mod;
int total = 1;
for(int i = 1; i <= m; i ++)total = total * 26 % mod;
cout<<(total - ans + mod) % mod<<endl;
return 0;
}
poj 3691
问修改量最少的方法使得 在母串中不存在任何子串。
还是一道记录一下end就行了的简单dp,
转移仍然是 字符串 匹配最经典的转移: 当母串在i, 子串在j时的balabala, 当然这道题的balabala就是母串最少的修改次数了, 很简单很经典。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define MAXN 5005
using namespace std;
int n, cnt, root, next[MAXN][5], fail[MAXN], f[1005][MAXN];
bool end[MAXN];
char s[1005];
inline int newnode(){
memset(next[cnt], -1, sizeof(next[cnt]));
end[cnt ++] = 0; return cnt - 1;
}
inline void insert(char s[]){
int p = root, len = strlen(s);
for(int i = 0; i < len; i ++){
int c;
if(s[i] == 'A')c = 0;
if(s[i] == 'C')c = 1;
if(s[i] == 'G')c = 2;
if(s[i] == 'T')c = 3;
if(next[p][c] == -1)next[p][c] = newnode();
p = next[p][c];
} end[p] = 1;
}
queue<int>q;
void build(){
fail[root] = root;
for(int i = 0; i <= 3; i ++){
if(next[root][i] == -1)next[root][i] = root;
else fail[next[root][i]] = root, q.push(next[root][i]);
}
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i <= 3; i ++){
if(next[u][i] == -1)next[u][i] = next[fail[u]][i];
else fail[next[u][i]] = next[fail[u]][i], end[next[u][i]] |= end[next[fail[u]][i]], q.push(next[u][i]);
}
}
}
int main(){
int cas = 0;
while(scanf("%d", &n) && n){
cnt = 0; root = newnode();
for(int i = 1; i <= n; i ++)scanf("%s", s), insert(s);
build();
scanf("%s", s + 1);
int len = strlen(s + 1);
for(int i = 1; i <= len; i ++){
if(s[i] == 'A')s[i] = 0;
if(s[i] == 'C')s[i] = 1;
if(s[i] == 'G')s[i] = 2;
if(s[i] == 'T')s[i] = 3;
}
for(int i = 1; i <= len; i ++)for(int j = 0; j <= cnt; j ++)f[i][j] = MAXN;
f[0][0] = 0;
for(int i = 0; i < len; i ++)
for(int j = 0; j < cnt; j ++)if(f[i][j] != MAXN){
for(int k = 0; k <= 3; k ++)
if(!end[next[j][k]])
f[i + 1][next[j][k]] = min(f[i + 1][next[j][k]], f[i][j] + (k != s[i + 1]));
}
int ans = MAXN;
for(int i = 0; i < cnt; i ++)ans = min(ans, f[len][i]);
printf("Case %d: ", ++ cas);if(ans == MAXN)puts("-1"); else cout<<ans<<endl;
}
return 0;
}
hdu 3341 lost's revenge
也是 很简单的转移, 把四维的数组压成一维 就可以啦, 还没有写呢有时间补上