ac自动机
https://www.cnblogs.com/sclbgw7/p/9875671.html
讲的好的博客。
只有fail指针的模板,好像用不到,一般都是用last指针的模板。
void build() { queue<int>q; q.push(1); while(!q.empty()) { int x=q.front();q.pop(); for(int i=0;i<26;++i) { int c=ch[x][i]; if(!c){ch[x][i]=ch[fail[x]][i];continue;}//关键,把子节点改成fail节点的子节点 q.push(c); int fa=fail[x]; while(fa&&!ch[fa][i])fa=fail[fa]; fail[c]=ch[fa][i]; } } }
https://cn.vjudge.net/contest/301351#problem/A
习题
最基础的模板,即求一个文本串有多少个匹配的模式串。
#include<queue> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 5e5 + 5; const int N = 1e6 + 5; int tree[maxn][27]; int val[maxn]; int f[maxn]; int last[maxn]; int tot = 1; void inset(char s[]) { int root = 0; int len = strlen(s); for(int i = 0; i < len; i++) { int id = s[i] - 'a'; if(!tree[root][id]) { memset(tree[tot], 0, sizeof(tree[tot])); val[tot] = 0; tree[root][id] = tot++; } root = tree[root][id]; } val[root]++; } void getfail() { queue<int> q; f[0] = 0; for(int i = 0; i < 26; i++) { int u = tree[0][i]; if(u) { f[u] = 0; q.push(u); last[u] = 0; } } while(!q.empty()) { int now = q.front(); q.pop(); for(int i = 0; i < 26; i++) { int u = tree[now][i]; if(!u) { tree[now][i] = tree[f[now] ][i]; continue; } q.push(u); int v = f[now]; f[u] = tree[v][i]; last[u] = val[f[u] ] ? f[u] : last[f[u] ]; } } } int query(char s[]) { int now = 0, ans = 0; int len = strlen(s); for(int i = 0; i < len; i++) { now = tree[now][s[i] - 'a' ]; // for(int j = now; j && val[j] != -1; j = last[j]) { // ans += val[j]; // val[j] = -1; // } int tmp = now;
//如果匹配到根节点就停止,其他情况下如果val[tmp]等于0或1都加上,0加上不影响结果。 while(tmp) { ans += val[tmp]; val[tmp] = 0; tmp = last[tmp]; } } return ans; } char s[N]; int main() { int n, t; scanf("%d", &t); while(t--) { scanf("%d", &n); tot = 1; memset(tree[0], 0, sizeof(tree[0])); for(int i = 0; i < n; i++) { scanf("%s", s); inset(s); } getfail(); scanf("%s", s); printf("%d\n", query(s)); } return 0; }
DNA Sequence
https://www.cnblogs.com/LQLlulu/p/9344774.html
一到ac自动机加上矩阵快速幂的题目
题意
题目给出m(m<=10)个仅仅由A,T,C,G组成的单词(单词长度不超过10),然后给出一个整数n(n<=2000000000),问你用这四个字母组成一个长度为n的长文本,有多少种组成方法可以使得它不含任何一个给出的单词。
分析
当时一看以为是跟训练指南上(UVA11468)一样的题,感觉只有四个字母并且单词数量和长度也比较小,但是一看给出的n有点懵逼。如果再按照书上建立AC自动机以后直接跑DP的方法肯定是不行了。然后我们就要用到,递推利器,矩阵快速幂。
我们还是按照套路先把AC自动机建出来,然后将每个单词结点设为非法结点,题目变成在AC自动机中走n步不通过非法结点的方案数。然后设f[i][j]是当前在结点i,已经走了j步,且未走过非法结点的方案数。然后怎么转移呢?
f[i][j]=A(0,i)*f[0][j-1]+A(1,i)*f[1][j-1]+...+A(sz-1,i)f[sz-1][j-1]。其中A(i,j)的含义就是从i到j有几条直接连接的边。那么将这个dp方程拆开来看
f0[i]=A(0,0)*f[0][i-1]+A(1,0)*f[1][i-1]+....+A(sz-1,0)*fsz-1[i-1]
f1[i]=A(0,1)*f[0][i-1]+A(1,1)*f[1][i-1]+....+A(sz-1,1)*fsz-1[i-1]
.
.
fsz-1[i]=A(0,sz-1)*f[0][i-1]+A(1,sz-1)*f[1][i-1]+...+A(sz-1,sz-1)*fsz-1[i-1]
然后根据这个我们就很好建立转移矩阵
*
我的理解:每一项表示i到j的直接路径条数,如果两个矩阵相乘,就是i到j经过两条路径的种类数,最后只要得到0到其他点经过路径条数为n的数量,所以就只需要第一行的值相加就行。
然后为什么都要不是单词末尾。(不是很理解)现在的想法,因为最后的路径是由0开始一直走下来的,所以就不能经过单词末尾的节点,那些节点都是无效的。
(第一次学会用公式编辑器不过好像还是贼丑)
然后建立这个大的转移矩阵,矩阵的(i,j)为结点i到结点j的直接路径的条数,然后跑一个矩阵快速幂。
最后把从0到sz-1结点的f(n)的值全部加起来就是答案了。
对了这个题需要用long long
下面是代码
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <queue> #include <map> using namespace std; const int maxnode=1100; const int MOD=100000; map<char,int>M; struct AC_Automata{ int ch[maxnode][4],f[maxnode],last[maxnode],val[maxnode],match[maxnode]; int sz; int idx(char c){ return M[c]; } void init(){ sz=1; memset(ch[0],0,sizeof(ch[0])); memset(match,0,sizeof(match)); val[0]=0; } void insert(char *s){ int n=strlen(s),u=0; for(int i=0;i<n;i++){ int c=idx(s[i]); if(!ch[u][c]){ ch[u][c]=sz; memset(ch[sz],0,sizeof(ch[sz])); val[sz++]=0; } u=ch[u][c]; } val[u]=1; match[u]=1; } void getFail(){ queue<int>q; last[0]=f[0]=0; for(int i=0;i<4;i++){ int u=ch[0][i]; if(u){ q.push(u); f[u]=last[u]=0; } } while(!q.empty()){ int r=q.front();q.pop(); for(int i=0;i<4;i++){ int u=ch[r][i]; if(!u){ ch[r][i]=ch[f[r]][i]; continue; } q.push(u); int v=f[r]; while(v&&!ch[v][i])v=f[v]; f[u]=ch[v][i]; match[u]|=match[f[u]]; } } } }ac; const int maxN=101; struct Matrix{ long long a[maxN][maxN]; void init(){ memset(a,0,sizeof(a)); for(int i=0;i<ac.sz;i++) a[i][i]=1; } }; Matrix mul(Matrix a,Matrix b){ Matrix res; for(int i=0;i<ac.sz;i++){ for(int j=0;j<ac.sz;j++){ res.a[i][j]=0; for(int k=0;k<ac.sz;k++){ res.a[i][j]+=a.a[i][k]*b.a[k][j]; res.a[i][j]%=MOD; } } } return res; } Matrix qpow(Matrix a,int k){ Matrix res; res.init(); while(k){ if(k%2)res=mul(res,a); a=mul(a,a); k/=2; } return res; } int n,m; char s[30]; int main(){ M['A']=0,M['C']=1,M['T']=2,M['G']=3; ac.init(); scanf("%d%d",&m,&n); for(int i=1;i<=m;i++){ scanf("%s",s); ac.insert(s); } ac.getFail(); Matrix A; for(int i=0;i<ac.sz;i++){ if(!ac.match[i]) for(int j=0;j<4;j++){ int u=ac.ch[i][j]; if(!ac.match[u]) A.a[i][u]++; } } /*for(int i=0;i<ac.sz;i++){ for(int j=0;j<ac.sz;j++){ printf("%d ",A.a[i][j]); } printf("\n"); }*/ Matrix S; S.a[0][0]=1; Matrix ANS; ANS=qpow(A,n); ANS=mul(S,ANS); long long ans=0; for(int i=0;i<=ac.sz;i++) ans+=ANS.a[0][i]; printf("%d\n",ans%MOD); return 0; }
蓝书上217面的题,ac自动机+概率dp,用的记忆化搜索。
给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的随机字符串S(每次随机独立)。给出K个模式串,计算S不包含任何一个串的概率(即任何一个模板串都不是S的连续子串)
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int SIGMA_SIZE = 64; const int MAXNODE = 500; int idx[256]; char s[30][30]; double prob[SIGMA_SIZE]; int n; struct AhoCorasickAutomata { int ch[MAXNODE][SIGMA_SIZE]; int f[MAXNODE]; int last[MAXNODE]; int match[MAXNODE]; int sz; void init() { sz = 1; memset(ch[0],0,sizeof(ch[0])); } //把模式串建立一颗字典树 void insert(char *s) { int u = 0,n = strlen(s); for(int i=0; i<n; i++) { int c = idx[s[i]]; if(!ch[u][c]) { memset(ch[sz],0,sizeof(ch[sz])); match[sz] = 0; ch[u][c] = sz++; } u = ch[u][c]; } match[u] = 1; } //根据bfs得到fail指针与last指针,在字典树上还增加失配边,及ch[u][i]的值变化了,有些本来是零的,根据fail数组的值,可能与之后匹配到的边连在了一起。 void getFail() { queue<int> q; f[0] = 0;
//与根节点相连的边,先初始化一下,并把有的字母push进来。 for(int c=0; c<SIGMA_SIZE; c++) { int u = ch[0][c]; if(u) { f[u] = 0; q.push(u); last[u] = 0; } } while(!q.empty()) { int r = q.front(); q.pop(); for(int c=0; c<SIGMA_SIZE; c++) { int u = ch[r][c];
//代替了最上面那个模板的while循环,每次下去一层就根据fail指针来增加失配边。 if(!u) { ch[r][c]=ch[f[r]][c]; continue; } q.push(u); int v = f[r]; // while(v&&!ch[v][c]) // v = f[v];
f[u] = ch[v][c]; last[u] = match[f[u] ] ? f[u] : last[f[u] ];
//这个是看这个节点是否是一个模式串的末尾,如果根据last匹配到的点是末尾,那么这个点也是某个模式串的末尾,因为last指针要么指向根节点,要么指向一个与当前匹配有相同后缀的点,如果是last节点的话,last匹配
//后没有字符,但是fail指针后面可能会有字符。 match[u] |=match[last[u]];
//这里相当于记录这个点是否是一个字符串的 } } } }; AhoCorasickAutomata ac; double d[MAXNODE][105]; int vis[MAXNODE][105]; double getProb(int u,int l) { if(!l) return 1.0; if(vis[u][l]) return d[u][l]; vis[u][l] = 1; double& ans = d[u][l]; ans = 0.0; for(int i=0; i<n; i++) { if(!ac.match[ac.ch[u][i]]) ans += prob[i]*getProb(ac.ch[u][i],l-1); } return ans; } int main() { int T; scanf("%d",&T); for(int kase = 1; kase<=T; kase ++) { int k,l; scanf("%d",&k); for(int i=0; i<k; i++) scanf("%s",s[i]); scanf("%d",&n); for(int i=0; i<n; i++) { char ch[9]; scanf("%s%lf",ch,&prob[i]); idx[ch[0]] = i; } ac.init(); for(int i=0; i<k; i++) ac.insert(s[i]); ac.getFail(); scanf("%d",&l); memset(vis,0,sizeof(vis)); printf("Case #%d: %.6lf\n",kase,getProb(0,l)); } return 0; }
DNA repair
ac自动机+dp;
跟矩阵那题思想差不多,只是这题要用dp来求最小值。
题意:有些DNA序列是致病的,给你以个长DNA序列,问最少需要修改多少次使得其不含致病序列。
首先是进行了一步转换,将其理解为在建好的Trie图上走len(序列长度)步,走的路不能包含致病序列。
然后就变成了一个dp,d[i][j]表示第i步走到j节点最少需要修改几次。那么要看所有能走到j节点的点,如果和s[i-1]相同,就不用修改,如果不同,修改次数加一,取较小的。
#include <bits/stdc++.h> using namespace std; #define CLR(m,a) memset(m,a,sizeof(m)) const int inf=0x3f3f3f3f; const int maxnode=50*20+10; const int sigma=4; struct AC{ int ch[maxnode][sigma],f[maxnode]; int match[maxnode]; int sz; int mp[128]; void init(){ CLR(match,0); mp['A']=0;mp['G']=1;mp['C']=2;mp['T']=3; CLR(ch[0],0); sz=1; } void inser(char *s){ int u=0,n=strlen(s); for(int i=0;i<n;i++){ int c=mp[s[i]]; if(!ch[u][c]){ CLR(ch[sz],0); ch[u][c]=sz++; } u=ch[u][c]; } match[u]=1; } void getfail(){ queue<int> q; f[0]=0; match[0]=0; for(int c=0;c<sigma;c++){ int u=ch[0][c]; if(u){ q.push(u); f[u]=0; } } while(!q.empty()){ int r=q.front(); q.pop(); for(int c=0;c<sigma;c++){ int u=ch[r][c]; if(!u){ ch[r][c]=ch[f[r]][c]; continue; } q.push(u); int v=f[r]; while(v&&!ch[v][c]) v=f[v]; f[u]=ch[v][c]; match[u]|=match[f[u]]; } } } }; AC ac; char s[maxnode]; int len; int dp[maxnode][maxnode]; void DP(){ CLR(dp,inf); dp[0][0]=0; for(int i=1;i<=len;i++){ for(int j=0;j<ac.sz;j++){ if(ac.match[j]) continue; for(int c=0;c<sigma;c++){ if(ac.match[ac.ch[j][c]]) continue; dp[i][ac.ch[j][c]]=min(dp[i][ac.ch[j][c]],dp[i-1][j]+(ac.mp[s[i-1]]!=c)); } } } } int main(){ int n; int kase=0; while(scanf("%d",&n)!=EOF&&n){ ac.init(); for(int i=0;i<n;i++){ scanf("%s",s); ac.inser(s); } ac.getfail(); scanf("%s",s); len=strlen(s); DP(); int ans=inf; for(int i=0;i<ac.sz;i++)if(!ac.match[i]){ ans=min(dp[len][i],ans); } if(ans==inf) ans=-1; printf("Case %d: %d\n",++kase,ans); } return 0; }