AC自动机专题小结
2017-12-17 22:24
316 查看
最近比较忙,AC自动机专题花了两个大周才勉强推完
关于与AC结合的一些题型如下:
1.AC自动机模板题 废话
2.AC自动机结合dp 经常会和矩阵联系起来或是一些转移的预处理(trie图),但都比较裸
3.AC自动机加fail树 个人理解是前缀树上的后缀树
模板题就不说了
但是发现串的长度很大
这种题的一般思路是:
1.先敲暴力
2.矩阵快速幂优化
矩阵A[i][j]的意思是dp[k][i]对dp[k][j]的贡献
(适用于所有dp+矩阵的题
fail树主要是利用它的一个dfs序,好在线段树上更新形如 [ ]+str 的串
然后在trie树上,又可以枚举形如str+[ ]的串
二者结合,就可以解决很多子串包含问题,即从自己的子串上更新一些值
思考点在于后缀与前缀的结合
若要查询c是否满足是前缀是a,后缀是b
其实可以这样构造
a#b c#c
然后只用判断a#b是否是c#c的子串即可
还有一些小方法与小结论:
1.若一个单词对答案只有至多一次贡献,则可以直接标记一个mark
若为-1则跳过,这样的复杂度就缩为O(len)
2.若是多篇文章对于一个字典的查询,且单词对每个文章的贡献最多为1,则可以用tmp数组记录最迟贡献时间,然后在while(now)中判断即可
3.若单词对文章有多次贡献,可利用fail树,然后dfs序解,复杂度同为O(len)
但代码可能会复杂一些
关于与AC结合的一些题型如下:
1.AC自动机模板题 废话
2.AC自动机结合dp 经常会和矩阵联系起来或是一些转移的预处理(trie图),但都比较裸
3.AC自动机加fail树 个人理解是前缀树上的后缀树
模板题就不说了
Problem J
可以看出是道dp题但是发现串的长度很大
这种题的一般思路是:
1.先敲暴力
2.矩阵快速幂优化
矩阵A[i][j]的意思是dp[k][i]对dp[k][j]的贡献
(适用于所有dp+矩阵的题
#include<cstdio> #include<cstring> using namespace std; // The code is by Zerokei #define FOR(i,x,y) for(int i=(x),i##_end_=(y);i<=i##_end_;i++) #define DOR(i,x,y) for(int i=(x),i##_end_=(y);i>=i##_end_;i--) #define P 100000 int Hash(char c){ if(c=='A')return 1; if(c=='C')return 2; if(c=='T')return 3; return 0; } void Add(int &x,int y){ x+=y; if(x>=P)x-=P; } struct Mat{ int n,m; int A[105][105]; Mat(){memset(A,0,sizeof A);} void Clear(){memset(A,0,sizeof A);} Mat operator *(const Mat &B){ Mat res; res.n=n; FOR(i,0,n)FOR(k,0,n) if(A[i][k])FOR(j,0,n){ Add(res.A[i][j],1ll*A[i][k]*B.A[k][j]%P); } return res; } void print(){ FOR(i,0,n){ FOR(j,0,n)printf("%d ",A[i][j]); puts(""); } puts(""); } }S; struct Aho_Corasick{ static const int Len=105; int pre[Len][4],Q[Len],F[Len],fail[Len]; int Sum[Len][Len]; int tot,L,R; int newnode(){ tot++; FOR(i,0,3)pre[tot][i]=0; fail[tot]=F[tot]=0; return tot; } void init(){ tot=-1; newnode(); } void insert(char *str){ int len=strlen(str),cur=0; FOR(i,0,len-1){ int k=Hash(str[i]); if(!pre[cur][k])pre[cur][k]=newnode(); cur=pre[cur][k]; } F[cur]=1; } void build(){ L=R=0; FOR(i,0,3)if(pre[0][i])Q[++R]=pre[0][i]; while(L<R){ int k=Q[++L]; F[k]|=F[fail[k]]; FOR(i,0,3){ int &p=pre[k][i]; if(p)fail[p]=pre[fail[k]][i],Q[++R]=p; else p=pre[fail[k]][i]; } } } void Make(){ S.Clear(); S.n=tot; FOR(i,0,tot)FOR(j,0,3){ int k=pre[i][j]; if(!F[k])S.A[i][k]++; } } void DP(int n){ Mat ans; ans.n=tot; ans.A[0][0]=1; while(n){ if(n&1)ans=ans*S; n>>=1; S=S*S; } int res=0; FOR(i,0,tot)Add(res,ans.A[0][i]); printf("%d\n",res); } }ACA; char str[15]; int main(){ int m,n; scanf("%d%d",&m,&n); ACA.init(); FOR(i,1,m){ scanf("%s",str); ACA.insert(str); } ACA.build(); ACA.Make(); ACA.DP(n); return 0; }
Problem O
trie树+fail树+线段树fail树主要是利用它的一个dfs序,好在线段树上更新形如 [ ]+str 的串
然后在trie树上,又可以枚举形如str+[ ]的串
二者结合,就可以解决很多子串包含问题,即从自己的子串上更新一些值
#include<bits/stdc++.h> using namespace std; #define FOR(i,x,y) for(int i=(x),i##_END_=(y);i<=i##_END_;i++) #define DOR(i,x,y) for(int i=(x),i##_END_=(y);i>=i##_END_;i--) #define M 300005 #define N 20005 int Max(int x,int y){return x>y?x:y;} void chk_mx(int &x,int y){if(x<y)x=y;} string str ; int len ,Val ; int n; //dfn int Lt[M],Rt[M],tta; //Link int Head[M],nxt[M],To[M],ttaE; void addedge(int a,int b){nxt[++ttaE]=Head[a];Head[a]=ttaE;To[ttaE]=b;} #define LFOR(i,x) for(int i=Head[x];i;i=nxt[i]) struct Segment_Tree{ #define Lson l,mid,p<<1 #define Rson mid+1,r,p<<1|1 struct node{int l,r,mx;}tree[M<<2]; void build(int l,int r,int p){ tree[p].l=l,tree[p].r=r,tree[p].mx=0; if(l==r)return; int mid=(l+r)>>1; build(Lson);build(Rson); } void update(int l,int r,int sum,int p){ if(tree[p].l==l&&tree[p].r==r){chk_mx(tree[p].mx,sum);return;} int mid=(tree[p].l+tree[p].r)>>1; if(mid>=r)update(l,r,sum,p<<1); else if(mid<l)update(l,r,sum,p<<1|1); else update(l,mid,sum,p<<1),update(mid+1,r,sum,p<<1|1); } int query(int x,int p){ if(tree[p].l==tree[p].r)return tree[p].mx; int mid=(tree[p].l+tree[p].r)>>1; if(mid>=x)return Max(tree[p].mx,query(x,p<<1)); else return Max(tree[p].mx,query(x,p<<1|1)); } }Tree; struct Aho_Carosick{ int Q[M],pre[M][26],fail[M]; int L,R,tot; int newnode(){ tot++; Head[tot]=fail[tot]=0; FOR(i,0,25)pre[tot][i]=0; return tot; } void init(){tot=-1;newnode();} void insert(int num){ cin>>str[num]; scanf("%d",&Val[num]); len[num]=str[num].size(); int cur=0; FOR(i,0,len[num]-1){ int k=str[num][i]-'a',&p=pre[cur][k]; if(!p)p=newnode(); cur=p; } } void build(){ L=R=0; FOR(i,0,25)if(pre[0][i])Q[++R]=pre[0][i]; while(L<R){ int k=Q[++L]; addedge(fail[k],k); FOR(i,0,25){ int &p=pre[k][i]; if(p)Q[++R]=p,fail[p]=pre[fail[k]][i]; else p=pre[fail[k]][i]; } } } void dfs(int x){ Lt[x]=++tta; LFOR(i,x)dfs(To[i]); Rt[x]=tta; } void Solve(){ Tree.build(1,tta,1); int ans=0; FOR(i,1,n){ int cur=0,res=0; FOR(j,0,len[i]-1){ int k=str[i][j]-'a'; cur=pre[cur][k]; chk_mx(res,Tree.query(Lt[cur],1)); } Tree.update(Lt[cur],Rt[cur],res+Val[i],1); chk_mx(ans,res+Val[i]); } printf("%d\n",ans); } }ACA; void Init(){ tta=ttaE=0; ACA.init(); } int main(){ int T; scanf("%d",&T); FOR(cas,1,T){ Init(); scanf("%d",&n); FOR(i,1,n)ACA.insert(i); ACA.build(); ACA.dfs(0); printf("Case #%d: ",cas); ACA.Solve(); } return 0; }
Problem P
这是道返璞归真的题目233思考点在于后缀与前缀的结合
若要查询c是否满足是前缀是a,后缀是b
其实可以这样构造
a#b c#c
然后只用判断a#b是否是c#c的子串即可
#include<bits/stdc++.h> using namespace std; #define FOR(i,x,y) for(int i=(x),i##_END_=(y);i<=i##_END_;i++) #define DOR(i,x,y) for(int i=(x),i##_END_=(y);i>=i##_END_;i--) #define M 100005 string S[M]; int pos[M]; struct Aho_Corasick{ #define N 600005 int pre [27],fail ,Q ,dep ,tmp ,pass ; int tot,L,R; void init(){ tot=-1; newnode(); } int newnode(){ tot++; FOR(i,0,26)pre[tot][i]=0; pass[tot]=fail[tot]=tmp[tot]=0; return tot; } void insert(int num){ char a ,b ; string c; scanf("%s",a); scanf("%s",b); c+=b;c+='{';c+=a; int len=c.size(),cur=0; FOR(i,0,len-1){ int k=c[i]-'a',&p=pre[cur][k]; if(!p)p=newnode(); cur=p; dep[p]=i; } pos[num]=cur; } void Build(){ L=R=0; FOR(i,0,26)if(pre[0][i])Q[++R]=pre[0][i]; while(L<R){ int k=Q[++L]; FOR(i,0,26){ int &p=pre[k][i]; if(p)Q[++R]=p,fail[p]=pre[fail[k]][i]; else p=pre[fail[k]][i]; } } } void Solve(string &str,int num){ int len=str.size(),cur=0; FOR(i,0,len-1){ cur=pre[cur][str[i]-'a']; int now=cur; while(now){ if(tmp[now]==num)break; if((len>>1)<dep[now]); else pass[now]++,tmp[now]=num; now=fail[now]; } } } }ACA; int main(){ int T; scanf("%d",&T); while(T--){ int n,m; scanf("%d%d",&n,&m); ACA.init(); FOR(i,1,n){ char s[100005]; scanf("%s",s); S[i]=s; S[i]+='{';S[i]+=s; } FOR(i,1,m)ACA.insert(i); ACA.Build(); FOR(i,1,n)ACA.Solve(S[i],i); FOR(i,1,m)printf("%d\n",ACA.pass[pos[i]]); } return 0; }
还有一些小方法与小结论:
1.若一个单词对答案只有至多一次贡献,则可以直接标记一个mark
若为-1则跳过,这样的复杂度就缩为O(len)
2.若是多篇文章对于一个字典的查询,且单词对每个文章的贡献最多为1,则可以用tmp数组记录最迟贡献时间,然后在while(now)中判断即可
3.若单词对文章有多次贡献,可利用fail树,然后dfs序解,复杂度同为O(len)
但代码可能会复杂一些
相关文章推荐
- AC自动机专题小结
- AC自动机专题——H - Text Generator SPOJ - GEN 矩阵快速幂+AC自动机
- kuangbin专题十七 HDU2896 病毒侵袭(AC自动机模板题)
- AC自动机专题——J - DNA repair HDU - 2457 DP+AC自动机
- AC自动机专题——K - Ring HDU - 2296 DP+AC自动机
- 字符串专题:A - Keywords Search(AC自动机)
- AC自动机专题——L - Wireless Password HDU - 2825 状压DP+AC自动机
- AC自动机专题——P - 考研路茫茫――单词情结 HDU - 2243 矩阵快速幂+AC自动机
- AC自动机专题训练
- CUGB专题训练之数据结构:F - 病毒侵袭持续中(AC自动机重复子串)
- AC自动机专题——A - Keywords Search HDU - 2222
- AC自动机专题——E - Censored! URAL - 1158 大数+DP+AC自动机
- ac自动机小结
- AC自动机专题——B - 病毒侵袭 HDU - 2896
- 【专题】字符串专题小结(AC自动机 + 后缀自动机)
- AC自动机专题
- AC自动机专题——C - 病毒侵袭持续中 HDU - 3065
- 初等字符串匹配专题小结[KMP][Manacher][Tire Tree][AC Automation]
- AC自动机专题
- AC自动机专题