「
「
「字符串算法
」
」
」第
5
5
5章
A
C
AC
AC自动机
(
(
(前
3
3
3题
)
)
)
目录:
A.单词查询
B.单词频率
C.前缀匹配
A . A. A. 例题 1 1 1 单词查询
分析:
A
C
AC
AC自动机模板 一般用来求 有多少个不同的模式串 在文本串里出现 这类问题
可以简单理解成 将
K
M
P
KMP
KMP放在
t
r
i
e
trie
trie树上
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int n,tot,q[N],lens,lena;
char s[N],a[N];
struct Trie{
int qwq,end,son[30];
}trie[N];
void insert(char ovo[])
{
int now=0;
for(int i=0;i<lens;i++)
{
int k=ovo[i]-'a';
if(!trie[now].son[k]) trie[now].son[k]=++tot;
now=trie[now].son[k];
}
trie[now].end++;
}
void work()
{
int l=0,r=0;
for(int i=0;i<26;i++)
if(trie[0].son[i]!=0)
{
trie[trie[0].son[i]].qwq=0;
q[++r]=trie[0].son[i];
}
while(l<r)
{
int front=q[++l];
for(int i=0;i<26;i++)
{
int x=trie[front].son[i];
if(x)
{
trie[x].qwq=trie[trie[front].qwq].son[i];
q[++r]=x;
}
else trie[front].son[i]=trie[trie[front].qwq].son[i];
}
}
}
int AC(char ovo[])
{
int now=0,ret=0;
for(int i=0;i<lena;i++)
{
int k=ovo[i]-'a';
now=trie[now].son[k];
int p=now;
while(p&&trie[p].end!=-1)
{
ret+=trie[p].end;
trie[p].end=-1;
p=trie[p].qwq;
}
}
return ret;
}
int main(){
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
lens=strlen(s);
insert(s);
}
trie[0].qwq=0;
work();
scanf("%s",a);
lena=strlen(a);
printf("%d\n",AC(a));
memset(trie,0,sizeof(trie));
}
return 0;
}
B . B. B. 例题 2 2 2 单词频率
分析:
此题可
f
i
n
d
(
)
find()
find()过 复杂度大约
O
(
N
2
∗
l
e
n
S
)
O(N^2*lenS)
O(N2∗lenS)
开了
O
2
O2
O2复杂度还是可观的
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=215;
string a[N];
int n,cnt[N],p;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int k=0;
p=a[j].find(a[i],k);
while(k<a[j].length()&&p!=-1)
{
k=p+1; //更新
p=a[j].find(a[i],k);
cnt[i]++;
}
}
for(int i=1;i<=n;i++)
printf("%d\n",cnt[i]);
return 0;
}
A C AC AC自动机:
把所有给出的单词 连成文本串 再求每个出现次数
那存
t
r
i
e
trie
trie时 就一直给当前单词加出现次数 然后正常
A
C
AC
AC自动机
f
a
i
l
fail
fail到这个点 就说明前面一段一定是匹配的
然后就在
f
a
i
l
fail
fail上求前缀和 就是次数了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int n,tot,sum[N],fail[N],num[N];
char s[N];
struct AC{
int end,son[27];
}trie[N];
void ins(int x)
{
scanf("%s",s+1);
int now=0,len=strlen(s+1);
for(int i=1;i<=len;i++)
{
int qwq=s[i]-'a';
if(!trie[now].son[qwq]) trie[now].son[qwq]=++tot;
now=trie[now].son[qwq];
sum[now]++;
}
trie[x].end=now; //记录位置
}
void build()
{
int head=0,tail=0;
for(int i=0;i<26;i++)
if(trie[0].son[i])
num[++tail]=trie[0].son[i];
while(head<tail)
{
int a=num[++head],b;
for(int i=0;i<26;i++)
{
b=trie[a].son[i];
if(b)
{
num[++tail]=b;
fail[b]=trie[fail[a]].son[i];
}else
trie[a].son[i]=trie[fail[a]].son[i];
}
}
}
void query()
{
for(int i=tot;i>=0;i--) sum[fail[num[i]]]+=sum[num[i]]; //求fail的前缀和
for(int i=1;i<=n;i++)
printf("%d\n",sum[trie[i].end]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
ins(i);
build();
query();
return 0;
}
C . C. C. 例题 3 3 3 前缀匹配
分析:
注意到 每个字符串长度都很长 肯定不能普通跑 A C AC AC自动机
我们就可以 枚举每个模式串的前缀 然后看有没有在文本串里出现
预处理就行了
如果有 那就找最长长度的前缀 就表示原本的模式串
然后就可以输出这个最长前缀
a
n
s
ans
ans了
因为它只有
4
4
4种字符 那就可以给每种字符一个
v
a
l
u
e
value
value 就可以节省空间
正常
−
-
−
′
A
′
'A'
′A′也可以 但效率会慢一些.
c
o
d
e
code
code是
−
-
−
′
A
′
'A'
′A′的
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
#define Ctnue continue
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e7+5,M=1e5+5;
int n,m,ans[M],tot;
char s[N],ch[M][105];
queue<int> q;
struct Trie{
int son[27],fail;
bool end;
}trie[N];
void add(int x)
{
int len=strlen(ch[x]),now=0;
for(int i=0;i<len;i++)
{
int qwq=ch[x][i]-'A';
if(!trie[now].son[qwq]) trie[now].son[qwq]=++tot;
now=trie[now].son[qwq];
}
}
void AC()
{
for(int i=0;i<=26;i++)
if(trie[0].son[i])
{
q.push(trie[0].son[i]);
trie[trie[0].son[i]].fail=0;
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<=26;i++)
{
if(trie[now].son[i])
{
q.push(trie[now].son[i]);
trie[trie[now].son[i]].fail=trie[trie[now].fail].son[i];
}
else trie[now].son[i]=trie[trie[now].fail].son[i];
}
}
}
void Pre()
{
int now=0;
for(int i=1;i<=n;i++) //枚举前缀
{
int qwq=s[i]-'A';
int x=trie[now].son[qwq];
while(x) //看有没有存在
{
trie[x].end=1;
x=trie[x].fail;
}
now=trie[now].son[qwq];
}
}
int query(int x)
{
int ans=0,len=strlen(ch[x]);
int now=0;
for(int i=0;i<len;i++)
{
int qwq=ch[x][i]-'A';
now=trie[now].son[qwq];
if(trie[now].end) ans=i+1; //枚举前缀的 最长长度
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
cin>>s[i];
for(int i=1;i<=m;i++)
{
scanf("%s",ch[i]);
add(i);
}
AC();Pre();
for(int i=1;i<=m;i++)
printf("%d\n",query(i));
return 0;
}