您的位置:首页 > 其它

AC自动机专题小结

2017-12-17 22:24 316 查看
最近比较忙,AC自动机专题花了两个大周才勉强推完

关于与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)

但代码可能会复杂一些
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: