AC自动机

对于多串匹配一种能够理论上时间复杂度为O(n+m)的多串匹配方式,但是时间复杂度并不稳定。

原理:在trie树上建立类似KMP的next指针的东西,也就是AC自动机的fail指针,在每次匹配的时候,不停的跳fail指针直到根节点。这是最裸的实现,但是许多情况下,这种最朴素的实现方式过不去...因为这样它的时间复杂度很不稳定,可能被卡到O(nm)这种模式下,我们就需要将AC自动机的BFS序拿出来,单独进行操作,甚至将fail指针建成fail树实现,还有什么打上差分标记之类的实现方式降低时间复杂度。

例题:

BZOJ3940: [Usaco2015 Feb]Censoring

分析:

AC自动机裸题,同年同月银组题是KMP+栈实现,而这道题是AC自动机+栈实现,因为题目满足模式串互不包含,所以直接贪心+栈模拟维护一下即可。

附上代码:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <queue>
#include <iostream>
using namespace std;
#define N 1000005
struct Aho
{
	int ch[N][26],last[N],fail[N],cnt,rot;
	int new_node(){memset(ch[cnt],-1,sizeof(ch[cnt]));last[cnt++]=0;return cnt-1;}
	void init(){cnt=0;rot=new_node();}
	void insert(char *s,int x)
	{
		int len=strlen(s),rt=0;
		for(int i=0;i<len;i++)
		{
			if(ch[rt][s[i]-'a']==-1)ch[rt][s[i]-'a']=new_node();
			rt=ch[rt][s[i]-'a'];
		}
		last[rt]=x;
	}
	void get_fail()
	{
		queue <int>q;fail[0]=0;
		for(int i=0;i<26;i++)
		{
			if(ch[0][i]==-1)ch[0][i]=0;
			else fail[ch[0][i]]=0,q.push(ch[0][i]);
		}
		while(!q.empty())
		{
			int x=q.front();q.pop();
			for(int i=0;i<26;i++)
			{
				if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
				else fail[ch[x][i]]=ch[fail[x]][i],q.push(ch[x][i]);
			}
		}
	}
}ac;
int n,m,len[N],sta[N],top,pos[N];
char sub[N],str[N];
int main()
{
	scanf("%s%d",str+1,&n);m=strlen(str+1);ac.init();
	for(int i=1;i<=n;i++)scanf("%s",sub),ac.insert(sub,i),len[i]=strlen(sub);
	ac.get_fail();int rt=0;
	for(int i=1;i<=m;i++)
	{
		rt=ac.ch[rt][str[i]-'a'];pos[++top]=rt,sta[top]=str[i];
		if(ac.last[rt])top-=len[ac.last[rt]],rt=pos[top];
	}
	for(int i=1;i<=top;i++)printf("%c",sta[i]);puts("");
	return 0;
}

BZOJ3172: [Tjoi2013]单词

分析:

这道题,最裸的AC自动机实现方式过不了...不用测试了,我试过了...

将AC自动机建起来,在每个节点打一个标记,每次插入的时候遍历到这个节点,这个节点的出现次数就++,之后每次讲节点的出现次数传递给fail节点,最后统计每一个串的终止节点出现次数即可。

附上代码:

#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
int ans[N];
struct Aho
{
    int ch[N][26],pos[N],fail[N],vis[N],cnt,rot,que[N],fa[N],l,r;
    int new_node(){memset(ch[cnt],-1,sizeof(ch[cnt]));vis[cnt++]=0;return cnt-1;}
    void init(){l=r=cnt=0,rot=new_node();}
    void insert(char *s,int x)
    {
        int len=strlen(s),rt=rot;
        for(int i=0;i<len;i++)
        {
            int t=s[i]-'a';
            if(ch[rt][t]==-1)ch[rt][t]=new_node();
            rt=ch[rt][t];vis[rt]++;
        }
        pos[x]=rt;
    }
    void get_fail()
    {
        for(int i=0;i<26;i++)
        {
            if(ch[0][i]==-1)ch[0][i]=0;
            else que[r++]=ch[0][i],fail[ch[0][i]]=0;
        }
        while(l<r)
        {
            int x=que[l++];
            for(int i=0;i<26;i++)
            {
                if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
                else fail[ch[x][i]]=ch[fail[x]][i],que[r++]=ch[x][i];
            }
        }
    }
    int match()
    {
        for(int i=cnt;i>=0;i--)
        {
            int x=que[i];
            vis[fail[x]]+=vis[x];
        }
    }
}ac;
char s[N];
int main()
{
    int n;
    scanf("%d",&n);ac.init();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);
        ac.insert(s,i);
    }
    ac.get_fail();ac.match();
    for(int i=1;i<=n;i++)
    {
        //printf("%d\n",ac.pos[i]);
        printf("%d\n",ac.vis[ac.pos[i]]);
    }
}

BZOJ1030: [JSOI2007]文本生成器

分析:

这道题看起来和GT考试很像,用全部的生成数量去掉完全不可读的数量,就是答案。全部的数量是26^m,而完全不可读的是类似GT考试的求法,建立AC自动机,之后类似遍历AC自动机的方式进行DP,顺便注意一点,即:如果跳fail指针能跳到某一个字符串的终止节点,那么这个节点就不能作为答案出现,即:我们统计所有的不存在一个终止节点作为祖先的节点。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 200005
#define mod 10007
int f[120][N],n,m;char s[N];
struct Aho
{
	int ch[N][26],fail[N],last[N],cnt,q[N],l,r,rot;
	int new_node(){memset(ch[cnt],-1,sizeof(ch[cnt]));last[cnt++]=0;return cnt-1;}
	void init(){cnt=0;rot=new_node();}
	void insert(char *s)
	{
		int len=strlen(s),rt=rot;
		for(int i=0;i<len;i++)
		{
			int t=s[i]-'A';
			if(ch[rt][t]==-1)ch[rt][t]=new_node();
			rt=ch[rt][t];
		}
		last[rt]=1;
	}
	void get_fail()
	{
		l=r=0;
		for(int i=0;i<26;i++)
		{
			if(ch[0][i]==-1)ch[0][i]=rot;
			else q[r++]=ch[0][i],fail[ch[0][i]]=rot;
			last[0]|=last[fail[0]];
		}
		while(l<r)
		{
			int x=q[l++];
			for(int i=0;i<26;i++)
			{
				if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
				else q[r++]=ch[x][i],fail[ch[x][i]]=ch[fail[x]][i];
			}
			last[x]|=last[fail[x]];
		}
	}
	int match()
	{
		f[0][0]=1;
		for(int i=1;i<=m;i++)
			for(int j=0;j<cnt;j++)
				if(!last[j]&&f[i-1][j])
					for(int k=0;k<26;k++)
						f[i][ch[j][k]]=(f[i][ch[j][k]]+f[i-1][j])%mod;
		int num=1,ans=0;
		for(int i=1;i<=m;i++)num=num*26%mod;
		for(int i=0;i<cnt;i++)
		{
			if(!last[i])ans=(ans+f[m][i])%mod;
		}
		return (num-ans+mod)%mod;
	}
}ac;
int main()
{
	scanf("%d%d",&n,&m);ac.init();
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);ac.insert(s);
	}
	ac.get_fail();
	printf("%d\n",ac.match());
	return 0;
}

BZOJ2553: [BeiJing2011]禁忌

分析:

看到len<=10^9就知道和矩阵乘法有关。建立矩阵,如果一个节点是终止节点(或者它的祖先存在终止节点),那么将矩阵i,0变成抽到对应字符的概率,和i,cnt也变成对应概率,如果不是终止节点,那么将i和对应子节点的矩阵改成概率即可。之后矩阵乘法实现一下即可。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 2005
#define mod 10007
int n,m,alpha,cnt;char s[N];
struct node
{
    long double a[100][100];
    friend node operator*(const node &a,const node &b)
    {
        node c;memset(c.a,0,sizeof(c.a));
        for(int i=0;i<=cnt;i++)
        {
            for(int j=0;j<=cnt;j++)
            {
                for(int k=0;k<=cnt;k++)
                {
                    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]);
                }
            }
        }
        return c;
    }
    void print()
    {
        for(int i=0;i<=cnt;i++)
        {
            for(int j=0;j<=cnt;j++)
            {
                printf("%.5lf ",a[i][j]);
            }
            puts("");
        }
    }
}ret,map;
double q_pow(int n)
{
    for(int i=0;i<=cnt;i++)ret.a[i][i]=1;
    while(n)
    {
        if(n&1)ret=ret*map;
        map=map*map;n=n>>1;
    }
    return ret.a[0][cnt];
}
struct Aho
{
    int ch[N][26],fail[N],last[N],q[N],l,r,rot;
    int new_node(){memset(ch[cnt],-1,sizeof(ch[cnt]));last[cnt++]=0;return cnt-1;}
    void init(){cnt=0;rot=new_node();}
    void insert(char *s)
    {
        int len=strlen(s),rt=rot;
        for(int i=0;i<len;i++)
        {
            int t=s[i]-'a';
            if(ch[rt][t]==-1)ch[rt][t]=new_node();
            rt=ch[rt][t];
        }
        //printf("%d",rt);
        last[rt]=1;
    }
    void get_fail()
    {
        l=r=0;
        for(int i=0;i<alpha;i++)
        {
            if(ch[0][i]==-1)ch[0][i]=rot;
            else q[r++]=ch[0][i],fail[ch[0][i]]=rot;
            last[0]|=last[fail[0]];
        }
        while(l<r)
        {
            int x=q[l++];
            for(int i=0;i<alpha;i++)
            {
                if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
                else q[r++]=ch[x][i],fail[ch[x][i]]=ch[fail[x]][i];
            }
            last[x]|=last[fail[x]];
        }
    }
    void match()
    {
        long double addv=1.0/(1.0*alpha);
        for(int i=0;i<cnt;i++)
        {
            for(int j=0;j<alpha;j++)
            {
                if(last[ch[i][j]])map.a[i][0]+=addv,map.a[i][cnt]+=addv;
                else map.a[i][ch[i][j]]+=addv;
            }
        }
        map.a[cnt][cnt]=1;
    }
}ac;
int main()
{
    scanf("%d%d%d",&n,&m,&alpha);ac.init();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);ac.insert(s);
    }
    ac.get_fail();ac.match();//map.print();
    printf("%.10f\n",q_pow(m));
    return 0;
}

BZOJ2938: [Poi2000]病毒

分析:

如果在trie树上存在一个环,那么就一定可以出现无穷的情况。判一下环是否存在即可。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 30005
struct Aho
{
    int ch[N][2],fail[N],last[N],cnt,rot,vis[N],inq[N];
    int new_node(){ch[cnt][1]=ch[cnt][0]=-1;last[cnt++]=0;return cnt-1;}
    void init(){cnt=0;rot=new_node();}
    void insert(char *s)
    {
        int len=strlen(s),rt=rot;
        for(int i=0;i<len;i++)
        {
            int t=s[i]-'0';
            if(ch[rt][t]==-1)ch[rt][t]=new_node();
            rt=ch[rt][t];
        }
        last[rt]=1;
    }
    void get_fail()
    {
        queue <int>q;
        for(int i=0;i<2;i++)
        {
            if(ch[0][i]==-1)ch[0][i]=0;
            else fail[ch[0][i]]=0,q.push(ch[0][i]);
        }
        while(!q.empty())
        {
            int x=q.front();q.pop();
            for(int i=0;i<2;i++)
            {
                if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
                else fail[ch[x][i]]=ch[fail[x]][i],q.push(ch[x][i]);
            }
            last[x]|=last[fail[x]];
        }
    }
    int match(int x)
    {
        inq[x]=vis[x]=1;
        for(int i=0;i<2;i++)
        {
            int t=ch[x][i];
            if(inq[t]||((!last[t])&&(!vis[t])&&match(t)))return 1;
        }
        inq[x]=0;
        return 0;
    }
}ac;
char s[N];
int main()
{
    int n;scanf("%d",&n);ac.init();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);ac.insert(s);
    }
    ac.get_fail();
    if(ac.match(ac.rot))puts("TAK");
    else puts("NIE");
    return 0;
}

  

BZOJ2434: [Noi2011]阿狸的打字机

分析:

先将给你的串建立成AC自动机,之后单独拎出fail树,求出每个节点在fail树上的入栈出栈序维护出来,之后再遍历一遍所有节点,将每个节点对应的询问求出即可。而正确性是因为fail树的每个节点的父节点的串都是这个节点的串的后缀。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 100005
struct node
{
    int to,next,val;
}ask[N],e[N];
int head[N],head_ask[N],cnt2,cnt1,in1[N],tims,out1[N],sum[N<<1],Q,flg[N],ans[N];char s[N];
void add(int x,int y){e[cnt2].to=y;e[cnt2].next=head[x];head[x]=cnt2++;}
void add_ask(int x,int y,int z){ask[cnt1].to=y;ask[cnt1].next=head_ask[x];ask[cnt1].val=z;head_ask[x]=cnt1++;}
void fix(int x,int c){for(;x<=tims;x+=x&-x)sum[x]+=c;}
int find(int x){int ret=0;for(;x;x-=x&-x)ret+=sum[x];return ret;}
struct Aho
{
    int ch[N][26],fail[N],q[N],last[N],rot,cnt,fa[N];
    int new_node(){memset(ch[cnt],-1,sizeof(ch[cnt]));last[cnt++]=0;return cnt-1;}
    void init(){cnt=0,rot=new_node();}
    void insert(char *s)
    {
        int len=strlen(s),rt=rot,tot=0;
        for(int i=0;i<len;i++)
        {
            if(s[i]=='B')rt=fa[rt];
            else if(s[i]=='P')last[rt]++,flg[++tot]=rt;
            else
            {
                if(ch[rt][s[i]-'a']==-1)ch[rt][s[i]-'a']=new_node(),fa[cnt-1]=rt;
                rt=ch[rt][s[i]-'a'];
            }
        }
    }
    void get_fail()
    {
        int l=0,r=0;
        for(int i=0;i<26;i++)
        {
            if(ch[0][i]==-1)ch[0][i]=0;
            else fail[ch[0][i]]=0,q[r++]=ch[0][i];
        }
        while(l<r)
        {
            int x=q[l++];
            for(int i=0;i<26;i++)
            {
                if(ch[x][i]==-1)ch[x][i]=ch[fail[x]][i];
                else fail[ch[x][i]]=ch[fail[x]][i],q[r++]=ch[x][i];
            }
        }
        for(int i=1;i<cnt;i++)add(fail[i],i);//printf("%d %d\n",fail[i],i);
    }
    void solve(char *s)
    {
        for(int i=0,len=strlen(s),rt=0;i<len;i++)
        {
            if(s[i]=='B')fix(out1[rt],-1),rt=fa[rt];
            else if(s[i]=='P')
            {
                for(int j=head_ask[rt];j!=-1;j=ask[j].next)
                {
                    int to1=ask[j].to,v=ask[j].val;
                    ans[v]=find(out1[to1])-find(in1[to1]-1);
                }
            }else
            {
                rt=ch[rt][s[i]-'a'];
                fix(in1[rt],1);
            }
        }
    }
}ac;
void dfs(int x)
{
    in1[x]=++tims;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        dfs(e[i].to);
    }
    out1[x]=++tims;
}
int main()
{
    scanf("%s%d",s,&Q);memset(head,-1,sizeof(head));memset(head_ask,-1,sizeof(head_ask));
    ac.init();ac.insert(s);ac.get_fail();dfs(0);
    for(int i=1;i<=Q;i++){int x,y;scanf("%d%d",&x,&y);add_ask(flg[y],flg[x],i);}ac.solve(s);
    for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);return 0;
 

  

posted @ 2018-06-19 07:52  Winniechen  阅读(211)  评论(0编辑  收藏  举报