• AC自动机学习小结


    AC自动机

    简要说明

    • (AC) 自动机,全称 (Aho-Corasick automaton) ,是一种有限状态自动机,应用于多模式串匹配.在 (OI) 中通常搭配 (dp) 食用.因为它是状态自动机.
    • 感性理解:在 (Trie) 树上加上 (fail) 指针.具体的讲解可以去看dalao们的博客(因为我实在是太菜了讲不好).

    题目

    • 题目:给若干个模式串,再给一个文本串,问有几个模式串在文本串中出现过.
    • 板子题.注意一个模式串只被计算一次,统计后做上标记.
    • 这里采用的是补全 (Trie) 树的写法.
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=5e5+10;
    const int Siz=26;
    struct AhoCorasick{
    	int idx;
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int val[MAXN];
    	void init()
    		{
    			idx=0;
    			memset(val,0,sizeof val);
    			memset(ch,0,sizeof ch);
    			memset(fail,0,sizeof fail);
    		}
    	AhoCorasick()
    		{
    			init();
    		}
    	void ins(char s[],int n)
    		{
    			int u=0;
    			for(int i=0;i<n;++i)
    				{
    					int k=s[i]-'a';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			++val[u];
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    				}
    		}
    	int query(char s[],int n)
    		{
    			int u=0,res=0;
    			for(int i=0;i<n;++i)
    				{
    					int k=s[i]-'a';
    					u=ch[u][k];
    					for(int j=u;j && val[j]!=-1;j=fail[j])
    						res+=val[j],val[j]=-1;
    				}
    			return res;
    		}
    }ac;
    char buf[MAXN<<1];
    int main()
    {
    	int T=read();
    	while(T--)
    		{
    			ac.init();
    			int n=read();
    			for(int i=1;i<=n;++i)
    				{
    					scanf("%s",buf);
    					ac.ins(buf,strlen(buf));
    				}
    			ac.getfail();
    			scanf("%s",buf);
    			int ans=ac.query(buf,strlen(buf));
    			printf("%d
    ",ans);
    		}
    	return 0;
    }
    

    玄武密码

    • 题意:给若干模式串和一个文本串.求每个模式串在文本串上能匹配的最大前缀长度.
    • 将模式串建成一个 (AC) 自动机,匹配文本串的时候往前暴力跳,跳到第一个合法的位置即可.
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e7+10;
    const int Siz=4;
    int n,m;
    int len[MAXN];
    struct AhoCorasick{
    	int idx;
    	inline int id(char x)
    		{
    			if(x=='E')
    				return 0;
    			if(x=='W')
    				return 1;
    			if(x=='N')
    				return 2;
    			return 3;
    		}
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int marked[MAXN];
    	int f[MAXN];
    	int val[MAXN];
    	AhoCorasick()
    		{
    			idx=0;
    			memset(fail,0,sizeof fail);
    			memset(ch,0,sizeof ch);
    			memset(marked,0,sizeof marked);
    			memset(val,0,sizeof val);
    		}
    	void ins(char s[],int v)
    		{
    			int u=0;
    			for(int i=0;i<len[v];++i)
    				{
    					int k=id(s[i]);
    					if(!ch[u][k])
    						{
    							f[++idx]=u;
    							ch[u][k]=idx;
    						}
    					u=ch[u][k];
    				}
    			val[v]=u;
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    				}
    		}
    	void mark(char *s)
    		{
    			int u=0;
    			for(int i=0;i<n;++i)
    				{
    					int k=id(s[i]);
    					u=ch[u][k];
    					for(int j=u;j;j=fail[j])
    						{
    							if(marked[j])
    								break;
    							marked[j]=1;
    						}
    				}
    		}
    	int work(int x)
    		{
    			int ans=len[x];
    			for(int i=val[x];i;i=f[i],ans--)
    				if(marked[i])
    					return ans;
    		}
    }ac;
    char buf[110];
    char s[MAXN];
    int main()
    {
    	n=read(),m=read();
    	scanf("%s",s);
    	for(int i=1;i<=m;++i)
    		{
    			scanf("%s",buf);
    			len[i]=strlen(buf);
    			ac.ins(buf,i);
    		}
    	ac.getfail();
    	ac.mark(s);
    	for(int i=1;i<=m;++i)
    		{
    			int ans=ac.work(i);
    			printf("%d
    ",ans);
    		}
    	return 0;
    }
    

    Censoring

    • 题意:给若干模式串和一个文本串.每次从文本串开头找到一个模式串,将其删去,直到无法删去为止,求出最后剩余的文本.保证任一个模式串中没有其他模式串.
    • 将模式串建成一个 (AC) 自动机,用一个栈来维护当前剩余的字符.匹配成功时直接修改栈顶.同时需要维护每个节点在 (Trie) 树上的位置.这样删除后可以立即得出当前的位置 (u) .
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e5+10;
    const int Siz=26;
    int len[MAXN];
    struct AhoCorasick{
    	int idx;
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int val[MAXN];
    	AhoCorasick()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			memset(fail,0,sizeof fail);
    			memset(val,0,sizeof val);
    		}
    	void ins(char *s,int n,int v)
    		{
    			int u=0;
    			for(int i=0;i<n;++i)
    				{
    					int k=s[i]-'a';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			val[u]=v;
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    				}
    		}
    	void solve(char *s,int n)
    		{
    			int u=0;
    			char stk[MAXN];
    			int tp=0;
    			int lst[MAXN];
    			for(int i=0;i<n;++i)
    				{
    					int k=s[i]-'a';
    					stk[++tp]=s[i];
    					lst[tp]=ch[u][k];
    					if(val[lst[tp]])
    						tp-=val[lst[tp]];
    					u=lst[tp];
    				}
    			for(int i=1;i<=tp;++i)
    				printf("%c",stk[i]);
    			puts("");
    		}
    }ac;
    char buf[MAXN];
    char s[MAXN];
    int main()
    {
    	scanf("%s",s);
    	int n=read();
    	for(int i=1;i<=n;++i)
    		{
    			scanf("%s",buf);
    			int len=strlen(buf);
    			ac.ins(buf,len,len);
    		}
    	ac.getfail();
    	ac.solve(s,strlen(s));
    	return 0;
    }
    

    单词

    • 题意:给出一个由若干单词组成的单词表,问每个单词在这个表中出现了几次.
    • 很像一个 (kmp) 或是 (AC) 自动机裸题,然而并没有那么简单.用自动机做 (n) 次匹配,可能会被卡掉.如果一直跳 (fail) 指针,就可以构造一组数据让你一直跳.
    • 正确的做法是使用 (fail) 树,连出这样所有的有向边 (fail[x]->x) .自动机上,每个节点都代表了一个前缀,连出边后,可以发现父亲节点是儿子节点的后缀.
    • 而一个字符串被匹配的次数恰好等于以它为后缀的前缀数目,即 (fail) 树中子树的大小.
    • 插入字符串的时候将经过的每个节点的权值 (+1) ,最后 (dfs) 统计即可.
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=1e6+10;
    const int Siz=26;
    int n;
    struct AhoCorasick{
    	int idx;
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int pos[MAXN];
    	int val[MAXN];
    	int ecnt;
    	int head[MAXN],to[MAXN],nx[MAXN],siz[MAXN];
    	AhoCorasick()
    		{
    			idx=0;
    			ecnt=0;
    			memset(ch,0,sizeof ch);
    			memset(fail,0,sizeof fail);
    			memset(val,0,sizeof val);
    			memset(head,0,sizeof head);
    		}
    	void ins(char *s,int len,int v)
    		{
    			int u=0;
    			for(int i=0;i<len;++i)
    				{
    					int k=s[i]-'a';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    					++val[u];
    				}
    			pos[v]=u;
    		}
    	void addedge(int u,int v)
    		{
    			++ecnt;
    			nx[ecnt]=head[u];
    			to[ecnt]=v;
    			head[u]=ecnt;
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					addedge(fail[u],u);
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    				}
    		}
    	void dfs(int u)
    		{
    			siz[u]=val[u];
    			for(int i=head[u];i;i=nx[i])
    				{
    					int v=to[i];
    					dfs(v);
    					siz[u]+=siz[v];
    				}
    		}
    	void pr()
    		{
    			for(int i=1;i<=n;++i)
    				printf("%d
    ",siz[pos[i]]);
    		}
    }ac;
    char buf[MAXN];
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;++i)
    		{
    			scanf("%s",buf);
    			ac.ins(buf,strlen(buf),i);
    		}
    	ac.getfail();
    	ac.dfs(0);
    	ac.pr();
    	return 0;
    }
    

    病毒

    • 题意:给出若干个 (01) 串,问是否存在一个无限长的 (01) 串,满足所有给出的串都不是它的子串.
    • 将给出的串插入到 (AC) 自动机里,那么若存在一个环,环上的节点及它们沿 (fail) 指针向上跳都不经过单词末节点,则符合要求.
    • 插入的时候将权值一起合并,最后做一次 (dfs) 即可.
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int MAXN=3e4+10;
    const int Siz=2;
    struct AhoCorasick{
    	int idx;
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int val[MAXN];
    	AhoCorasick()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			memset(val,0,sizeof val);
    			memset(fail,0,sizeof fail);
    		}
    	void ins(char *s,int len)
    		{
    			int u=0;
    			for(int i=0;i<len;++i)
    				{
    					int k=s[i]-'0';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			val[u]=1;
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									val[ch[u][i]]|=val[ch[fail[u]][i]];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    				}
    		}
    	int vis[MAXN],inc[MAXN];
    	int dfs(int u)
    		{
    			inc[u]=1;
    			int v;
    			for(int i=0;i<Siz;++i)	
    				{
    					v=ch[u][i];
    					if(inc[v])
    						return 1;
    					if(vis[v] || val[v])
    						continue;
    					vis[v]=1;
    					if(dfs(v))
    						return 1;
    				}
    			inc[u]=0;
    			return 0;
    		}
    	void solve()
    		{
    			memset(vis,0,sizeof vis);
    			memset(inc,0,sizeof inc);
    			if(dfs(0))
    				puts("TAK");
    			else
    				puts("NIE");
    		}
    }ac;
    int n;
    char buf[MAXN];
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;++i)
    		{
    			scanf("%s",buf);
    			ac.ins(buf,strlen(buf));			
    		}
    	ac.getfail();
    	ac.solve();
    	return 0;
    }
    

    文本生成器

    • 题意:给出若干个由大写字母构成的单词,问长度为 (m) ,由大写字母构成的字符串中,包含至少一个单词的数目.对 (10007) 取模.
    • 可以先求出不包含任意一个单词的字符串数目,再用总数目(26^m)减去.
    • 将单词建成一个 (AC) 自动机,类似上题,合并权值即可求出一个节点是否能被走到.
    • (f[i][j]) 表示已经走了 (i) 步,走到了节点 (j) 时的方案数. (O(n^2) dp) 即可.
    #include"bits/stdc++.h"
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int P=1e4+7;
    inline int add(int a,int b)
    {
    	return (a+b) % P;
    }
    inline int mul(int a,int b)
    {
    	return a * b % P;
    }
    int fpow(int a,int b)
    {
    	int res=1;
    	while(b)
    		{
    			if(b&1)
    				res=mul(a,res);
    			a=mul(a,a);
    			b>>=1;
    		}
    	return res;
    }
    const int MAXN=7777;
    const int Siz=26;
    int n,m;
    struct AhoCorasick{
    	int idx;
    	int ch[MAXN][Siz];
    	int fail[MAXN];
    	int val[MAXN];
    	int f[101][MAXN];
    	AhoCorasick()
    		{
    			idx=0;
    			memset(ch,0,sizeof ch);
    			memset(fail,0,sizeof fail);
    			memset(val,0,sizeof val);
    			memset(f,-1,sizeof f);
    		}
    	void ins(char *s,int len)
    		{
    			int u=0;
    			for(int i=0;i<len;++i)
    				{
    					int k=s[i]-'A';
    					if(!ch[u][k])
    						ch[u][k]=++idx;
    					u=ch[u][k];
    				}
    			val[u]=1;
    		}
    	void getfail()
    		{
    			queue<int> q;
    			for(int i=0;i<Siz;++i)
    				if(ch[0][i])
    					q.push(ch[0][i]);
    			while(!q.empty())
    				{
    					int u=q.front();
    					q.pop();
    					for(int i=0;i<Siz;++i)
    						{
    							if(ch[u][i])
    								{
    									fail[ch[u][i]]=ch[fail[u]][i];
    									//val[ch[u][i]]|=fail[ch[u][i]];
    									q.push(ch[u][i]);
    								}
    							else
    								ch[u][i]=ch[fail[u]][i];
    						}
    					val[u]|=val[fail[u]];//注意. 
    				}
    		}
    	int dfs(int i,int j)//已经走了i步,在节点j 
    		{
    			if(f[i][j]!=-1)
    				return f[i][j];
    			if(val[j])
    				return 0;
    			if(i==m)
    				return 1;
    			int &res=f[i][j];
    			res=0;
    			for(int k=0;k<Siz;++k)
    				{
    					res=add(res,dfs(i+1,ch[j][k]));
    				}
    			return res;
    		}
    	void solve()
    		{
    			int ans=fpow(26,m);
    			ans=add(ans,P-dfs(0,0));
    			printf("%d
    ",ans);			
    		}
    }ac;
    char buf[MAXN];
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=n;++i)
    		{
    			scanf("%s",buf);
    			ac.ins(buf,strlen(buf));
    		}
    	ac.getfail();
    	ac.solve();
    	return 0;
    }
    

    小结

    • (AC) 自动机可以解决一部分多模式串有关问题.其附带品 (fail) 树也有不错的性质.
  • 相关阅读:
    jquery选择器
    js中的闭包技术
    idea创建servlet不能创建:
    JSP页面不解析EL表达式的原因
    大对象数据LoB的应用
    缓冲流、转换流、序列化流相关流知识点
    jdk5.0新特性(注解)
    EKT相关知识(Class类对象的方法补充)
    java中调用存储过程或函数
    Java 缓冲流
  • 原文地址:https://www.cnblogs.com/jklover/p/10251543.html
Copyright © 2020-2023  润新知