AC自动机

AC 自动机 - OI Wiki (oi-wiki.org)

给定一个长度为m主串再给出n个平均长度为w模式串问这些模式串分别出现了多少次。

如果对n个模式串分别进行kmp算法那么时间复杂度:n次匹配 每次(m+w) 所以是O(nm+nw)

ac自动机时间复杂度: 建树O(w*n) 建立fail数组 O(w*n) 匹配O(w*m) 所以是O(wm+nw)

所以可知当n相对于w很大即模式串的数量较模式串的平均长度很大时就应该用AC自动机来解决

对于字典树我们知道,字典树是用主串建树,然后对于各个模式串分别在主串上面跑一遍,而ac自动机是对于模式串建树,然后把主串在模式串上面跑一遍。

AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程。
如果你对KMP算法和了解的话,应该知道KMP算法中的next函数(shift函数或者fail函数)是干什么用的。KMP中我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符,当A[i+1]≠B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配,而next函数恰恰记录了这个j应该调整到的位置。同样AC自动机的失败指针具有同样的功能,也就是说当我们的模式串在Tire上进行匹配时,如果与当前节点的关键字不能继续匹配的时候,就应该去当前节点的失败指针所指向的节点继续进行匹配。

举例:

AC自动机_第1张图片

以要查找的单词建树:say he shr her  

初始化

const int N = 1e6 + 6;
int n;
char s[N];
namespace AC {
int tr[N][26], tot;
int e[N], fail[N];

建树

void insert(char *s) {
  int u = 0;
  for (int i = 1; s[i]; i++) {
    if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;  //如果没有则插入新节点
    u = tr[u][s[i] - 'a'];                              //搜索下一个节点
  }
  e[u]++;  //尾为节点 u 的串的个数
}

构造失败指针

queueq;
void build() {
  for (int i = 0; i < 26; i++)
    if (tr[0][i]) q.push(tr[0][i]);
  while (q.size()) {
    int u = q.front();
    q.pop();
    for (int i = 0; i < 26; i++) {
      if (tr[u][i]) {
        fail[tr[u][i]] =
tr[fail[u]][i];  // fail数组:同一字符可以匹配的其他位置 这里的tr[fail[u][i]]就是所求的最长后缀
        q.push(tr[u][i]);
      } else
        tr[u][i] = tr[fail[u]][i];
    }
  }
}

AC自动机_第2张图片

fail[i]=j 说明以i为终止节点的单词的最长后缀是以j为终止节点的单词,所以fail[u]表示当前主串匹配到了u这个位置,那么fail[u]这个位置也一定匹配成功了,如匹配到了she的e,那么he也一定匹配成功了

匹配

这里  作为字典树上当前匹配到的结点,res 即返回的答案。循环遍历匹配串, 在字典树上跟踪当前字符。利用 fail 指针找出所有匹配的模式串,累加到答案中。然后清零。在上文中我们分析过,字典树的结构其实就是一个 trans 函数,而构建好这个函数后,在匹配字符串的过程中,我们会舍弃部分前缀达到最低限度的匹配。fail 指针则指向了更多的匹配状态。最后上一份图。对于刚才的自动机:

AC自动机_第3张图片

int query(char *t) {
  int u = 0, res = 0;
  for (int i = 1; t[i]; i++) {
    u = tr[u][t[i] - 'a'];  // 转移
    for (int j = u; j && e[j] != -1; j = fail[j]) {
      res += e[j], e[j] = -1;
    }
  }
  return res;
}
}  

hdu3065 链接

#include
using namespace std;
const int maxn=2e5+5;
int u=0;
int tree[maxn][26];
int fail[maxn];
int e[maxn];
mapmp;
mapcheck;
vectorvec;
void build(char s[]){
	int len=strlen(s);
	int h=0;
	for(int i=0;iq;
void build_fail(){
	for(int i=0;i<26;i++){
		if(tree[0][i]){
			q.push(tree[0][i]);
		}
	}
	while(q.size()){
		int now=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			if(tree[now][i]){
				fail[tree[now][i]]=tree[fail[now]][i];
				q.push(tree[now][i]);
			}
			else{
				tree[now][i]=tree[fail[now]][i];
			}
		}
	}
}
void find(string s){
    int u=0;int res=0;
	for(int i=0;i>n;
	while(n--){
		char s[55];
		cin>>s;
		vec.push_back(s);
		build(s);
	}
	build_fail();
	string tmp;cin>>tmp;
	find(tmp);
	for(int i=0;i

AC自动机的应用

例题

https://www.acwing.com/problem/content/submission/code_detail/15098046/

AC自动机_第4张图片

对于各个治病DNA片段可以当作是模式串,对于模式串建立ac自动机,然后对于主串进行修改,看最少要修改多少个位置可以(让主串与trie树没有匹配使得到了u这个模式的时候,u的后缀含有治病DNA片段),dp解决

#include
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=4e3+5;
const int inf=1e9+7;
int n;
int tot;
int cnt=0;
int tree[maxn][4];
int fail[maxn];
int st[maxn];
mapmp;
mapmpp;
string s;
int check[maxn][1005];
void insert(string s){
	int u=0;
	for(int i=0;iq;
	for(int i=0;i<4;i++){
		if(tree[u][i]){
			q.push(tree[u][i]);
		}
	}
	while(q.size()){
		int now=q.front();
		q.pop();
		for(int i=0;i<4;i++){
			if(tree[now][i]){
				fail[tree[now][i]]=tree[fail[now]][i];
				if(st[fail[tree[now][i]]]){
					st[tree[now][i]]=1;
				}
				q.push(tree[now][i]);
			}
			else{
				tree[now][i]=tree[fail[now]][i];
			}
		}
	}
}
int dp(int u,int len){
	if(len==s.size()){
		return check[u][len]=0;
	}
	if(check[u][len]!=-1){
		return check[u][len];
	}
	int res=inf;
	for(int i=0;i<4;i++){
		if(!st[tree[u][i]]){
			res=min(res,dp(tree[u][i],len+1)+(mpp[i]!=s[len]));
		}
	}
	return check[u][len]=res;
}
void solve(){
	memset(tree,0,sizeof(tree));
	memset(st,0,sizeof(st));
	memset(check,-1,sizeof(check));
	memset(fail,0,sizeof(fail));
	cnt=0;
	while(n--){
		cin>>s;
		insert(s);	
	}
	build_fail();
	cin>>s;
	int ans=dp(0,0);
	if(ans>=1e9){
		ans=-1;
	}
	cout<<"Case "<>n){
		if(n==0){
			break;
		}
		tot++;
		solve();
	}
}

你可能感兴趣的:(java,算法,开发语言)