【字符串】AC自动机
AC自动机
学习资料: OI Wiki
模板
解释 fail
指针:
将构建后的字典树的所有状态集合记作 \(Q\) ,
状态 \(u\) 的 fail 指针指向另一个状态 \(v\) ,其中 \(v\in Q\) ,且 \(v\) 是 \(u\) 的最长后缀(即在若干个后缀状态中取最长的一个作为 fail 指针)。
AC自动机在做匹配时,同一位上可匹配多个模式串。
struct AC{
int tr[maxn][26],cnt;
int fail[maxn];
int ed[maxn],num[maxn];//这两个是根据题意写的
void init(){
cnt=0;
mem(tr,0);mem(fail,0);
mem(ed,0);mem(num,0);
}
void insert(char s[],int len,int id)
{
int p=0;
for(int i=0;i<len;i++)
{
if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
p=tr[p][s[i]-'a'];
}
ed[p]=id;//对于字符串结尾节点的记录,根据题意记录 如这是记录该字符串的下标(属于第几个字符串)
// 根据题意,有时是单纯的bool标记,有时是记录长度 等等
}
void getfail()// 当完成插入时,需要 ac.getfaile() 跑一次。
{
queue<int>que;
for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
while(!que.empty())
{
int p=que.front();que.pop();
for(int i=0;i<26;i++)
{
if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
else tr[p][i]=tr[fail[p]][i];
}
}
}
// query 是文本串的匹配,根据题意写。
// 但无外乎就是这些操作
void query(char s[],int len,int n)
{
int j,p=0;
for(int i=0;i<len;i++)
{
j=p=tr[p][s[i]-'a'];
// while跑的是 以当前后缀不断遍历字典树里的所有相同后缀
while(j)
{
if(ed[j])
num[ed[j]]++;//这一句根据题意写
j=fail[j];
}
}
//之后的操作也是根据题意
}
}ac;
例题
题意:给 \(n\) 个模式串,之后给一个文本串。对每组数据,第一行输出模式串最多出现的次数,接下去若干行输出一个出现次数最多的模式串,按驶入顺序排列。
解:
对于每个状态终点记录一下该终点对应哪个模式串。之后遍历文本串的时候吗,在fail转移的时候,对每个状态终点记录其对应的出现次数。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=1e6+50;
char s[maxn];
char ss[220][110];
struct AC{
int tr[maxn][26],cnt;
int fail[maxn];
int ed[maxn],num[maxn];
void init(){
cnt=0;
mem(tr,0);mem(fail,0);
mem(ed,0);mem(num,0);
}
void insert(char s[],int len,int id)
{
int p=0;
for(int i=0;i<len;i++)
{
if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
p=tr[p][s[i]-'a'];
}
ed[p]=id;
}
void getfail()
{
queue<int>que;
for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
while(!que.empty())
{
int p=que.front();que.pop();
for(int i=0;i<26;i++)
{
if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
else tr[p][i]=tr[fail[p]][i];
}
}
}
void query(char s[],int len,int n)
{
int j,p=0;
for(int i=0;i<len;i++)
{
j=p=tr[p][s[i]-'a'];
while(j)
{
if(ed[j])num[ed[j]]++;
j=fail[j];
}
}
int ma=0;
for(int i=1;i<=n;i++)ma=max(ma,num[i]);
printf("%d\n",ma);
for(int i=1;i<=n;i++)if(num[i]==ma)puts(ss[i]);
}
}ac;
int main()
{
int n;
while(scanf("%d",&n)&&n)
{
ac.init();
for(int i=1;i<=n;i++)
{
scanf("%s",ss[i]);
ac.insert(ss[i],strlen(ss[i]),i);
}
ac.getfail();
scanf("%s",s);
ac.query(s,strlen(s),n);
}
}
题意:给 \(n\) 个模式串 \(a_i\) ,对于每个模式串 \(a_i\) 有一个价值 \(p_i\) 。之后一个文本串 \(T\) ,求问最小价值。如果不能通过模式串拼接成文本串,则输出 -1。
解:
ac自动机+dp
\(f[i]=min(f[i],f[i-len[p]]+val[i])\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
ll f[maxn];
int val[maxn],dep[maxn];
struct AC{
int tr[maxn][26],cnt;
int fail[maxn];
void insert(char s[],int len,int w)
{
int p=0;
for(int i=0;i<len;i++)
{
if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
p=tr[p][s[i]-'a'];
}
dep[p]=len;
if(!val[p])val[p]=w;
val[p]=min(val[p],w);
}
void getfail()
{
queue<int>que;
for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
while(!que.empty())
{
int p=que.front();que.pop();
for(int i=0;i<26;i++)
{
if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
else tr[p][i]=tr[fail[p]][i];
}
}
}
void query(char s[],int len)
{
int j,p=0;
for(int i=1;i<=len;i++)
{
j=p=tr[p][s[i]-'a'];
while(j)
{
if(dep[j])f[i]=min(f[i],f[i-dep[j]]+val[j]);
j=fail[j];
}
}
}
}ac;
char s[maxn];
int main()
{
int n,w,len;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s%d",s,&w);
ac.insert(s,strlen(s),w);
}
ac.getfail();
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;i++)f[i]=1e16;
ac.query(s,len=strlen(s+1));
printf("%lld\n",f[len]==1e16?-1:f[len]);
}