ac自动机及相关dp
2017-03-21 11:39
246 查看
终于会写ac自动机了。。。记得高一时充满了对这个算法的恐慌。。。
ac自动机的实际与kmp十分相像,不过它是有一堆匹配字符串。具体原理请见其他的博客。
首先,在szc大佬(%%%orz)的模板演示下,以及hzwer的模板参考下,终于自己独立写出了自己的模板(指针版真的遭不住)。。。(请神犇们orz不要嘲笑我这个蒟蒻。。)//在下的模板以hdu2222为准
然后贴两道例题
hdu2222(最裸的初学者必做题)
bzoj2754
由于可延伸点数过多,所以用map来存储。
之后便是ac自动机+dp了
一般是先将trie树建造并构造fail,然后在这颗树上做匹配问题。
主要类型(在下目前所见,会更新):
1.不能出现哪些串
在这些串的结尾表上一个danger,如果一个节点的fail有danger,那么它也有(因为当前结点的后缀是fail指向的串,所以说明到当前结点时,fail节点所代表的串在这里已经被包含了,所以若其不能走,此也不能走)。
dp[i][j]表示到节点j已经选取了长度为i的合法串的方法数,每次若子节点是danger的,就不转移,若这个字母没有在这个节点的儿子,便转移到root上就行了。
poj3691
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
//当然也可以像这样容斥
hdu2825
3.其他类型的dp
例如
bzoj2938
4.甚至可能只是简单的递推
bzoj3172
总之,只要搞清楚怎么在这个trie树上去转移,剩下的就看自己dp学的好不好了。。
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学的好不好了。。
相关文章推荐
- AC自动机相关Fail树和Trie图相关基础知识
- Android系统版本以及屏幕相关参数的获取包括长宽,物理尺寸,px,dp,dpi,ppi等,
- android 适配相关知识(一) -- density dpi px dp dip sp 解释
- hdu 4511 AC自动机 + dp
- HDU 1625 Censored! (AC自动机 + 大数 + dp)
- hdu4758 hdu2825 hdu4057 AC自动机与状态压缩dp的结合
- Android中的pix,sp,dp相关概念
- UVA - 11468 Substring (AC自动机 + 概率dp)
- UVaLive 3490 - Generator (AC自动机 期望DP 高斯消元)
- AC自动机与dp(poj3691)
- HDU 2457 (AC自动机 DP)
- HDU4511 小明系列故事——女友的考验(AC自动机 + DP)
- POJ 3042 区间DP(费用提前计算相关的DP)
- UVA 11468 Substring(AC自动机 + dp)
- hdu4758(ac自动机,状态压缩dp)
- HDU 2825 Wireless Password (AC自动机 + 状压dp)
- 【BZOJ3530】[Sdoi2014]数数 AC自动机+数位DP
- UVA - 11468 Substring ( AC自动机 + dp)
- Kattis taboo (AC自动机 拓扑排序 DP)
- ZOJ 3494 BCD Code (AC自动机 + 数位DP)