题目
题解
AC自动机
AC自动机第一题,感觉做起来非常不顺,感觉难点在于处理单词重叠的问题
大体的思路应该是,我们把每个单词出现的字母在fail树上进行累加,然后建立bfs序,把单词的字母的ans累加到单词的第一个字母上(应该是这样吧?),输出答案;
另一种实现方法:这是我刚开始想到的,在每个单词之间添加一个'#' '#' 分隔组成文章,在处理重叠单词上:我们的mp[k]表示与第k个单词相同的最靠前的单词的编号,这样用于处理重叠的情况;
好像还是使用的链表,比较巧妙
代码
AC自动机:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
const int maxn=2e6;
const int inf=1e9;
int n,pos[maxn],ans[maxn],bfsn[maxn];//bfs序
struct Tree{
int fail;
int vis[30];
}AC[maxn];
string s[maxn];
int cnt,tot;
void build_AC(string s,int Num)
{
int l=s.length(); int now=0;
for (int i=0; i<l; i++)
{
if (!AC[now].vis[s[i]-'a'])
AC[now].vis[s[i]-'a']=++cnt;
now=AC[now].vis[s[i]-'a'];
ans[now]++;
}
pos[Num]=now;//第Num个单词的结束位置
}
queue <int> q;
void Get_fail()
{
cnt=0;
for (int i=0; i<26; i++)
{
if (AC[0].vis[i])//这里!=0
{
AC[AC[0].vis[i]].fail=0;
q.push(AC[0].vis[i]);
}
}
while (!q.empty())
{
int now=q.front(); q.pop();
bfsn[++cnt]=now;//建立bfs序,深度单调不下降
for (int i=0; i<26; i++)
{
if (AC[now].vis[i])
{
AC[AC[now].vis[i]].fail=AC[AC[now].fail].vis[i];
q.push(AC[now].vis[i]);
}
else
AC[now].vis[i]=AC[AC[now].fail].vis[i];
}
}
}
void getans()
{
for (int i=cnt; i>=1; i--)
ans[AC[bfsn[i]].fail]+=ans[bfsn[i]];
}
int main()
{
scanf("%d",&n);
for (int i=1; i<=n; i++)
{
cin>>s[i];
build_AC(s[i],i);
}
Get_fail();
getans();
for (int i=1; i<=n; i++)
printf("%d\n",ans[pos[i]]);
return 0;
}
第二种实现方法:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
#define ll long long
const int maxn=2e6;
const int inf=1e9;
struct Tree{
int fail;
int vis[30];
}AC[maxn];
int n,all,last[maxn],next[maxn],mp[maxn],ans[maxn],head[maxn];
//next[i]表示第i个位置应该对应哪个最靠前的单词编号 mp[k]表示与第k个单词相同的最靠前的单词的编号,next、mp有一定的联系
//last[i]是当前位置的上一个位置 last归根到底是一个指针
string SS,s;
int cnt=0;
void build_AC(string s,int Num)
{
int now=0; int l=s.length();
SS+=s+'#'; all+=l+1;
for (int i=0; i<l; i++)
{
if (!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt;
now=AC[now].vis[s[i]-'a'];
}
if (!next[now]) next[now]=Num;
mp[Num]=next[now];
}
queue <int> q;
void Get_fail()
{
for (int i=0; i<26; i++)
{
if (AC[0].vis[i])
q.push(AC[0].vis[i]);
}
while (!q.empty())
{
int now=q.front(); q.pop();
for (int i=0; i<26; i++)
{
int to=AC[now].vis[i];
if (to)
{
AC[to].fail=AC[AC[now].fail].vis[i];
last[to] = next[AC[to].fail]?AC[to].fail:last[AC[to].fail];
q.push(to);
}
else AC[now].vis[i]=AC[AC[now].fail].vis[i];
}
}
}
void ques()
{
int now=0;
for (int i=0; i<all; i++)
{
if (SS[i]=='#')
{
now=0; continue;
}
now=AC[now].vis[SS[i]-'a'];
if (next[now]) ans[next[now]]++;
int to=last[now];
while (to)
{
ans[next[to]]++;
to=last[to];
}
}
}
int main()
{
scanf("%d",&n);
for (int i=1; i<=n; i++)
{
cin>>s;
build_AC(s,i);
}
Get_fail();
ques();
for (int i=1; i<=n; i++) printf("%d\n",ans[mp[i]]);
return 0;
}
总结
if (!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt;没有写-'a'又浪费了好长时间!
这道题还有hash、后缀数组的写法,以后再学吧