您的位置:首页 > 其它

ac自动机及相关dp

2017-03-21 11:39 246 查看
终于会写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
[26],fail
,value
;
bool vis
;
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
,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
;
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
;
map<int ,int > c
;
bool vis
,mark
;
int value
,fail
;
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
;
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
,ans2
;
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
[5];
int value
,cnt,fail
;
int dp[1005]
;
bool vis
;
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
;
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
[26],cnt;
int fail
;
int dp[105]
;
int value[
4000
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
;
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
;
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
[26],cnt,fail
,value
;
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
;
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
[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
[2],fail
,cnt,value
;
bool vis
;
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
;
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
;
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
;
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
[26],n,fail
,cnt;
int loc
,value
;
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
;
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
;
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学的好不好了。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: