ac自动机及相关dp

终于会写ac自动机了。。。记得高一时充满了对这个算法的恐慌。。。
ac自动机的实际与kmp十分相像,不过它是有一堆匹配字符串。具体原理请见其他的博客。
首先,在szc大佬(%%%orz)的模板演示下,以及hzwer的模板参考下,终于自己独立写出了自己的模板(指针版真的遭不住)。。。(请神犇们orz不要嘲笑我这个蒟蒻。。)//在下的模板以hdu2222为准
然后贴两道例题
hdu2222(最裸的初学者必做题)
#include<iostream>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
const int N=1000050;
int cnt,c[N][26],fail[N],value[N];
bool vis[N];
void init()
{
    cnt=1;
    memset(fail,0,sizeof(fail));
    memset(value,0,sizeof(value));
    memset(vis,0,sizeof(vis));
    memset(c,0,sizeof(c));
    for(int i=0;i<=25;i++)
        c[0][i]=1;
    fail[1]=0;
}
void ins(char *str)
{
    int len=strlen(str);
    int now=1;
    for(int i=0;i<len;i++)
    {
        int index=str[i]-'a';
        if(!c[now][index])
            c[now][index]=++cnt;
        now=c[now][index];
    }
    value[now]++;
}
int q[N],head,tail;
void buildac()
{
    head=tail=0;
    q[++tail]=1;
    while(head!=tail)
    {
        int now=q[++head];
        for(int i=0;i<=25;i++)
        {
            if(c[now][i])
            {
                int k=fail[now];
                while(c[k][i]==0)    k=fail[k];
                fail[c[now][i]]=c[k][i];
                q[++tail]=c[now][i];
            }
        }
    }
}
int solve(char *aim)
{
    int len=strlen(aim);
    int now=1;
    int index;
    int result=0;
    for(int i=0;i<len;i++)
    {
        index=aim[i]-'a';
        while(!c[now][index])    now=fail[now];
        now=c[now][index];
        int temp=now;
        while(temp!=1&&!vis[temp])
        {
            result+=value[temp];
            vis[temp]=1;
            temp=fail[temp];
        }
    }
    return result;
}
int T,n;
char cc[N];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%s",cc),ins(cc);    
        buildac();
        scanf("%s",cc);
        printf("%d\n",solve(cc));
    }
}

bzoj2754
由于可延伸点数过多,所以用map来存储。
#include<iostream>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<map>
#include<vector>
using namespace std;
const int N=100050;
const int M=20050;
vector<int> name[M],st[N];
map<int ,int > c[N];
bool vis[N],mark[N];
int value[N],fail[N];
int cnt;
void init()
{
	cnt=1;
	for(int i=-1;i<=10000;i++)
		c[0][i]=1;
	
}
void ins(int id)
{
	int len;
	scanf("%d",&len);
	int now=1,x;
	for(int i=0;i<len;i++)
	{
		scanf("%d",&x);
		if(!c[now][x])
			c[now][x]=++cnt;
		now=c[now][x];
	}
	st[now].push_back(id);
}
int q[N];
void buildac()
{
	int head=0,tail=0;
	int now;
	q[++tail]=1;
	while(head!=tail)
	{
		now=q[++head];
		for(map<int,int>::iterator i=c[now].begin();i!=c[now].end();i++ )
		{
			int t=i->first;
			int k=fail[now];
			while(!c[k][t]) k=fail[k];
			fail[i->second]=c[k][t];
			q[++tail]=i->second;
		}
	}
}
vector<int> tongji1,tongji2;
int n,m,L,x,ans1[N],ans2[N];
void get(int now,int id)
{
	for(int i=now;i!=1;i=fail[i])
	{
		if(!vis[i])
		{
			vis[i]=1,tongji1.push_back(i);
			for(int j=0;j<st[i].size();j++)
			{
				if(!mark[st[i][j]])
				{
					ans1[id]++;
					ans2[st[i][j]]++;
					mark[st[i][j]]=1;
					tongji2.push_back(st[i][j]);
				}
			}
		}
		else
			break;
	}
}
void solve(int g)
{
	int now=1;
	int len=name[g].size();
	for(int i=0;i<len;i++)
	{
		while(!c[now][name[g][i]])	now=fail[now];
		now=c[now][name[g][i]],get(now,g);
	}
	for(int i=0;i<tongji1.size();i++)
		vis[tongji1[i]]=0;
	for(int i=0;i<tongji2.size();i++)
		mark[tongji2[i]]=0;
		tongji1.clear();
		tongji2.clear();
}
int main()
{
	init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&L);
		for(int j=1;j<=L;j++)
			scanf("%d",&x),name[i].push_back(x);
		name[i].push_back(-1);
		scanf("%d",&L);
		for(int j=1;j<=L;j++)
			scanf("%d",&x),name[i].push_back(x);
	}
	for(int i=1;i<=m;i++)
		ins(i);
	buildac();
	for(int i=1;i<=n;i++)
		solve(i);
	for(int i=1;i<=m;i++)
		printf("%d\n",ans2[i]);
	for(int i=1;i<=n;i++)
	{
		printf("%d",ans1[i]);
		if(i!=n)
			printf(" ");
	}
}

之后便是ac自动机+dp了
一般是先将trie树建造并构造fail,然后在这颗树上做匹配问题。
主要类型(在下目前所见,会更新):
1.不能出现哪些串
在这些串的结尾表上一个danger,如果一个节点的fail有danger,那么它也有(因为当前结点的后缀是fail指向的串,所以说明到当前结点时,fail节点所代表的串在这里已经被包含了,所以若其不能走,此也不能走)。
dp[i][j]表示到节点j已经选取了长度为i的合法串的方法数,每次若子节点是danger的,就不转移,若这个字母没有在这个节点的儿子,便转移到root上就行了。
poj3691
#include<iostream>
#include<math.h>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
const int N=6050;
const int INF=0x3f3f3f3f;
int c[N][5];
int value[N],cnt,fail[N];
int dp[1005][N];
bool vis[N];
void init()
{
	memset(dp,0,sizeof(dp));
	memset(value,0,sizeof(value));
	memset(c,0,sizeof(c));
	memset(fail,0,sizeof(fail));
	for(int i=0;i<=3;i++)
		c[0][i]=1;
	cnt=1;
}
int getd(char s)
{
	if(s=='A')
		return 0;
	if(s=='C')
		return 1;
	if(s=='T')
		return 2;
	if(s=='G')
		return 3;
}
void ins(char *str)
{
	int len=strlen(str);
	int now=1;
	for(int i=0;i<len;i++)
	{
		int index=getd(str[i]);
		if(!c[now][index])
			c[now][index]=++cnt;
		now=c[now][index];
	}
	value[now]=1;
}
int q[N];
void buildac()
{
	int now,head=0,tail=0;
	q[++tail]=1;
	while(head!=tail)
	{
		now=q[++head];
		for(int i=0;i<=3;i++)
		{
			if(c[now][i])
			{
				int k=fail[now];
				while(!c[k][i])	k=fail[k];
				fail[c[now][i]]=c[k][i];
				q[++tail]=c[now][i];
				value[c[now][i]]|=value[c[k][i]];
			}
		}
	}
}
char st[1005];
int n;
int solve()
{
	int now=1;
	int len=strlen(st);
	for(int i=0;i<=len;i++)
		for(int j=1;j<=cnt;j++)
			dp[i][j]=INF;
	dp[0][1]=0;
	for(int i=1;i<=len;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			if(dp[i-1][j]!=INF)
			{
				for(int k=0;k<=3;k++)
				{
					if(c[j][k])
					{
						if(!value[c[j][k]])
							dp[i][c[j][k]]=min(dp[i][c[j][k]],dp[i-1][j]+(k!=getd(st[i-1])));
					}
					else 
					{
						int f=fail[j];
						while(!c[f][k]) f=fail[f];
						if(!value[c[f][k]])
							dp[i][c[f][k]]=min(dp[i][c[f][k]],dp[i-1][j]+(k!=getd(st[i-1])));
					}
				}
			}
		}
	}
	int minans=INF;
	for(int i=1;i<=cnt;i++)
		minans=min(minans,dp[len][i]);
	return minans==INF?-1:minans;
}
int test=0;
int main()
{
	while(scanf("%d",&n)==1)
	{
		
		if(n==0)
			break;
		init();
		for(int i=1;i<=n;i++)
			scanf("%s",st),ins(st);
		buildac();
		scanf("%s",st);
		printf("Case %d: %d\n",++test,solve());
	}
	
	
} 


2.要出现哪些串的方法数
这时候要使用状压dp来做,每一个节点维护一个value表示走到了这个节点能匹配哪些串,一个节点value要或 fail所指的value,原理同上。
dp[i][j][state]表示到j,选取了长度为i的串,state表示已经匹配了哪些串的方法数。如果不问方案数而问最大价值,value就维护成+=value[fail[now]]即可,毕竟是一个道理。
注意,无论是value还是danger应该在buildfail时维护,因为在维护fail时是bfs,所以当计算一个节点的value时,它的fail所指节点的value一定是完完全全的解决了(因为fail所指一定深度小于自己,毕竟bfs嘛),所以只需要关心fail的value即可。

bzoj1030
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int N=6050;
const int mo=10007;
int c[N][26],cnt;
int fail[N];
int dp[105][N];
int value[N];
void init()
{
	for(int i=0;i<=25;i++)
		c[0][i]=1;
	cnt=1;
	dp[0][1]=1;
}
void ins(char *str)
{
	int len=strlen(str);
	int now=1;
	for(int i=0;i<len;i++)
	{
		int index=str[i]-'A';
		if(!c[now][index])	c[now][index]=++cnt;
		now=c[now][index];
	}
	value[now]=1;
}
int q[N];
void buildac()
{
	int now,head=0,tail=0;
	q[++tail]=1;
	while(head!=tail)
	{
		now=q[++head];
		for(int i=0;i<=25;i++)
		{
			if(c[now][i])
			{
				int k=fail[now];
				while(!c[k][i])	k=fail[k];
				fail[c[now][i]]=c[k][i];
				value[c[now][i]]|=value[c[k][i]];
				q[++tail]=c[now][i];
			}
		}
	}
}
int n,m;
char st[N];
void dpx()
{
	for(int x=1;x<=m;x++)
	{
		for(int i=1;i<=cnt;i++)
		{
			if(!dp[x-1][i]||value[i])
				continue;
			for(int j=0;j<=25;j++)
			{
				int k=i;
				while(!c[k][j])	k=fail[k];
				dp[x][c[k][j]]=(dp[x][c[k][j]]+dp[x-1][i])%mo;
			}
		}
	}
}
int ans1,ans2;
int lpow(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)
			ans=(ans*a)%mo;
		a=(a*a)%mo;
		b>>=1;
	}
	return ans;
}
int main()
{
	init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%s",st),ins(st);
	buildac();
	dpx();
	int ans1=lpow(26,m);
	for(int i=1;i<=cnt;i++)
		if(!value[i]) ans2=(ans2+dp[m][i])%mo;
	printf("%d\n",(ans1-ans2+mo)%mo);
} 

//当然也可以像这样容斥
hdu2825
include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
using namespace std;
const int N=1010;
const int mo=20090717;
int c[N][26],cnt,fail[N],value[N];
long long dp[26][1100][105],ans;
void init()
{
    for(int i=1;i<=1001;i++)
        for(int j=0;j<=25;j++)
            c[i][j]=0;
    for(int i=1;i<=1001;i++)
        value[i]=0,fail[i]=0;
    for(int i=0;i<=25;i++)
        c[0][i]=1; 
    cnt=1;
    ans=0;
}
void ins(char *str,int id)
{
    int len=strlen(str);
    int now=1;
    for(int i=0;i<len;i++)
    {
        int index=str[i]-'a';
        if(!c[now][index])    c[now][index]=++cnt;
        now=c[now][index];
    }
    value[now]|=(1<<(id-1));
}
int q[N];
void buildac()
{
    int head=0,k,tail=0,now;
    q[++tail]=1;
    while(head!=tail)
    {
        int now=q[++head];
        for(int i=0;i<=25;i++)
        {
            if(c[now][i])
            {
                k=fail[now];
                while(!c[k][i])    k=fail[k];
                fail[c[now][i]]=c[k][i];
                q[++tail]=c[now][i];
                value[c[now][i]]|=value[fail[c[now][i]]];
            }
        }
    }
}
int n,m,f,state;
int tongji(int x)
{
    int as=0;
    while(x)
    {
        as+=(x&1);
        x>>=1;
    }
    return as;
}
void solve()
{
    for(int i=0;i<=n;i++)
        for(int j=0;j<=state;j++)
            for(int k=1;k<=cnt;k++)
                dp[i][j][k]=0LL;
    dp[0][0][1]=1LL;
    int t;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=state;j++)
        {
            for(int k=1;k<=cnt;k++)
            {
                if(!dp[i-1][j][k])
                    continue;
                for(int x=0;x<=25;x++)
                {
                    t=k;
                    while(!c[t][x]) t=fail[t];
                    dp[i][j|value[c[t][x]]][c[t][x]]=(dp[i][j|value[c[t][x]]][c[t][x]]+dp[i-1][j][k])%mo;
                }
            }    
        }
    }
    for(int i=0;i<=state;i++)
    {
        if(tongji(i)<f)
            continue;
        for(int j=1;j<=cnt;j++)
            ans=(ans+dp[n][i][j])%mo;
    }
}
char st[15];
int main()
{
    while(scanf("%d%d%d",&n,&m,&f)==3)
    {
        if(n==m&&m==f&&f==0)
            break;
        init();
        state=(1<<m)-1;
        for(int i=1;i<=m;i++)
            scanf("%s",st),ins(st,i);
        buildac();
        solve();
        printf("%lld\n",ans);
    }
}

3.其他类型的dp
例如
bzoj2938
#include<iostream>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int N=30050;
int c[N][2],fail[N],cnt,value[N];
bool vis[N];
void init()
{
	c[0][1]=c[0][0]=1;
	cnt=1;
}
void ins(char *a)
{
	int now=1;
	int len=strlen(a);
	for(int i=0;i<len;i++)
	{
		int index=a[i]-'0';
		if(!c[now][index])
			c[now][index]=++cnt;
		now=c[now][index];
	}
	value[now]=1;
}
int q[N];
void buildac()
{
	int now;
	int head=0,tail=0;
	q[++tail]=1;
	while(head!=tail)
	{
		int now=q[++head];
		for(int i=0;i<=1;i++)
		{
			if(c[now][i])
			{
				int k=fail[now];
				while(!c[k][i])	k=fail[k];
				fail[c[now][i]]=c[k][i];
				value[c[now][i]]|=value[c[k][i]];
				q[++tail]=c[now][i];	
			}
			else
				c[now][i]=c[fail[now]][i];
		}
	}
}
bool in[N];
bool  dfs(int x)
{
	in[x]=1;
	for(int i=0;i<=1;i++)
	{
		int v=c[x][i];
		if(in[v])	return 1;
		if(vis[v]||value[v])	continue;
		vis[v]=1;
		if(dfs(v)) return 1;
	}
	in[x]=0;
	return 0;
}
char st[N];
int n;
int main()
{
	scanf("%d",&n);
	init();
	for(int i=1;i<=n;i++)
		scanf("%s",st),ins(st);
	buildac();
	if(dfs(1))	printf("TAK\n");
	else printf("NIE\n");
}
4.甚至可能只是简单的递推
bzoj3172
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int N=1000050;
int c[N][26],n,fail[N],cnt;
int loc[N],value[N];
void init()
{
	for(int i=0;i<=25;i++)
		c[0][i]=1;
	cnt=1;
}
void ins(char *str,int id)
{
	int len=strlen(str);
	int now=1;
	for(int i=0;i<len;i++)
	{
		int index=str[i]-'a';
		if(!c[now][index])
			c[now][index]=++cnt;
		now=c[now][index];
		value[now]++;
	}
	loc[id]=now;
}
int q[N];
void buildac()
{
	int head=0,tail=0,now;
	q[++tail]=1;
	while(head!=tail)
	{
		now=q[++head];
		for(int i=0;i<=25;i++)
		{
			if(c[now][i])
			{
				int k=fail[now];
				while(!c[k][i]) k=fail[k];
				fail[c[now][i]]=c[k][i];
				q[++tail]=c[now][i];
			}
		}
	}
	for(int i=tail;i>=1;i--)
		value[fail[q[i]]]+=value[q[i]];
}
char st[N];
int main()
{
	init();
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%s",st),ins(st,i);	
	buildac();
	for(int i=1;i<=n;i++)
		printf("%d\n",value[loc[i]]);
}


总之,只要搞清楚怎么在这个trie树上去转移,剩下的就看自己dp学的好不好了。。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值