后缀自动机SAM学习笔记(应用)
后缀自动机
原理多而复杂,这里不叙述一遍了
两者配合食用效果极佳
应用
主要是来将一下应用的,只匹配子串的SAM题几乎没有,洛谷的模板题也是要经过处理才能实现的
先给个模板,用于判断子串是否存在
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int t[maxn][26];
int len[maxn],fa[maxn];
string s;
int lst=1,cnt=1;
void add(int c){
int p=lst,u=++cnt;
lst=u;
len[u]=len[lst]+1;
for(;p&&(!t[p][c]);p=fa[p]) t[p][c]=u;
if(!p){
fa[u]=1;
return;
}
int q=t[p][c];
if(len[q]==len[p]+1){
fa[u]=q;
return;
}
int v=++cnt;
len[v]=len[p]+1;
memcpy(t[v],t[q],sizeof(t[q]));
fa[v]=fa[q],fa[u]=fa[q]=v;
for(;p&&t[p][c]==q;p=fa[p]) t[p][c]=v;
return;
}
bool query(string& s){
int p=1;
for(int i=0;i<s.size();i++){
if(!t[p][s[i]-'a']) return 0;
p=t[p][s[i]-'a'];
}
return 1;
}
int main(){
cin>>s;
for(int i=0;i<s.size();i++) add(s[i]-'a');
string in;
while(true){
cin>>in;
if(query(in)) printf("Yes\n");
else printf("No\n");
}
return 0;
}
洛谷P3804
如何计算子串的出现个数?
在parent tree上,每个节点都是其子节点的真后缀
所以若子节点出现了,该节点也必定会出现
在建立parent tree时,在添加新的字符后,将当前序列(自动机已处理过的所有字符组成的串)的出现个数+1
具体表现为建立一个数组,将序列所对的新节点u的出现次数+1
在程序中写为f[x]=1,这是因为每一个这样的序列都会有一个自己的新节点,所以至多只加这一次
然后你会发现,你维护了一个树上差分数组!!!
对parent tree建图,dfs将差分数组转为统计出现次数的数组
对于每一个节点,len[i]表示最长子串长度,要求最大值,可以忽略同一节点上的其他串
统计答案:if(f[i]!=1) ans=max(ans,1ll*len[i]*f[i])
CODE
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int t[maxn][26];
int fa[maxn],len[maxn],f[maxn];
int lst=1,tot=1;
int head[maxn],nxt[maxn<<1],e[maxn<<1];
int cnt;
string s;
void init_mp(){
memset(head,-1,sizeof(head));
cnt=0;
}
void add_edge(int u,int v){
e[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
return;
}
void add(int c){
int p=lst,u=++tot;
lst=u,f[u]=1;
len[u]=len[p]+1;
for(;p&&(!t[p][c]);p=fa[p]) t[p][c]=u;
if(!p){
fa[u]=1;
return;
}
int q=t[p][c];
if(len[q]==len[p]+1){
fa[u]=q;
return;
}
int v=++tot;
len[v]=len[p]+1;
memcpy(t[v],t[q],sizeof(t[q]));
fa[v]=fa[q],fa[u]=fa[q]=v;
for(;p&&t[p][c]==q;p=fa[p]) t[p][c]=v;
return;
}
void dfs(int x){
for(int i=head[x];~i;i=nxt[i]){
dfs(e[i]);
f[x]+=f[e[i]];
}
return;
}
int main(){
cin>>s;
for(int i=0;i<s.size();i++) add(s[i]-'a');
init_mp();
for(int i=2;i<=tot;i++) add_edge(fa[i],i);
dfs(1);
long long ans=0;
for(int i=2;i<=tot;i++){
if(f[i]!=1) ans=max(ans,1ll*len[i]*f[i]);
}
printf("%lld",ans);
return 0;
}
洛谷P5546
这类问题在SAM中有两种做法
1.狭义上 很高级的叫法,毕竟另外一种网上叫广义 ,只对一个字符串(通常选取最短的)建自动机
然后把其他串放到自动机上去匹配。这种方式比较繁琐,数组也很多很杂,这里就不讨论了
感兴趣可以参考dummyummy的博客
2.广义上,把所有字符串连起来,对整个字符串建机,统计出现个数,找到满足f[i]==n的最长子串
这样做几个问题:
1.拼接以后相邻两个字符串之间会产生新的子串,会干扰结果
在每个字符串后面添加特殊字符,这里千万别都用一个相同的,考虑0,1,0,1...交替使用
证明:如果用一个相同的,假设每个字符串都一样,那么这个字符就有可能会被计算到公共字串当中
使用01时,即使被计算进去,最后也会因为个数不达标被淘汰掉
2.同一字符串内有多个相同的子串,干扰统计结果
使用bool记录该子串在哪些字符串中出现过,这类题目字符串个数一般较小,可以使用状态压缩优化
字符串的统计和洛谷P3804一样,树上差分+建图+dfs即可
CODE
#include<bits/stdc++.h>
using namespace std;
const int maxn=10002;
int t[maxn<<1][28];
int len[maxn<<1],fa[maxn<<1];
int f[maxn<<1];
string s[5];
int n;
int lst=1,idx=1;
void add(int x,int id){
int p=lst,u=++idx;
lst=u;
len[u]=len[p]+1;
f[u]|=(1<<id);
for(;p&&(!t[p][x]);p=fa[p]) t[p][x]=u;
if(!p){
fa[u]=1;
return;
}
int q=t[p][x];
if(len[q]==len[p]+1){
fa[u]=q;
return;
}
int v=++idx;
len[v]=len[p]+1;
memcpy(t[v],t[q],sizeof(t[q]));
fa[v]=fa[q],fa[u]=fa[q]=v;
for(;p&&t[p][x]==q;p=fa[p]) t[p][x]=v;
return;
}
int head[maxn<<1],nxt[maxn<<1],e[maxn<<1];
int cnt;
void init_mp(){
memset(head,-1,sizeof(head));
cnt=-1;
return;
}
void add_edge(int u,int v){
e[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
return;
}
void dfs(int u){
for(int i=head[u];~i;i=nxt[i]){
dfs(e[i]);
f[u]|=f[e[i]];
}
return;
}
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>s[i];
for(int i=0;i<n;i++){
for(int j=0;j<s[i].size();j++){
add(s[i][j]-'a',i);
}
add(26+i%2,i);
}
init_mp();
for(int i=2;i<=idx;i++){
add_edge(fa[i],i);
}
dfs(1);
int ac=(1<<n)-1;
int ans=0;
for(int i=1;i<=idx;i++){
if(f[i]==ac){
ans=max(ans,len[i]);
}
}
printf("%d",ans);
return 0;
}
其他例题继续更新中

浙公网安备 33010602011771号