ac自动机及相关dp

来源:互联网 发布:讯佳摇杆淘宝 编辑:程序博客网 时间:2024/06/05 22:44
终于会写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]);}}}elsebreak;}}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];}elsec[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