后缀自动机的应用
求不同子串个数
解题思路
这个其实就是在后缀自动机上统计不同的路径条数,可以\(dp\)解决,\(f[u]=\sum\limits_{v=son[u]} f[v]+1\),时间复杂度\(O(n)\)。突然发现这种太麻烦了。。直接枚举每个点,\(ans=\sum l[i]-l[fa[i]]\)就行了。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=200005;
typedef long long LL;
inline int rd(){
int x=0,f=1; char ch=getchar();
while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return f?x:-x;
}
int n,c[N],a[N],l[N],r[N],len;
LL f[N];
char s[N];
struct SAM{
int ch[N][28],fa[N],cnt,lst,l[N];
void insert(int c){
int p=lst,np=++cnt; lst=np; l[np]=l[p]+1;
for(;!ch[p][c] && p;p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=1;
else {
int q=ch[p][c];
if(l[q]==l[p]+1) fa[np]=q;
else {
int nq=++cnt; l[nq]=l[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q]; fa[np]=fa[q]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void build(){
for(int i=1;i<=n;i++) insert(s[i]-'a'+1);
for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
}
void solve(){
for(int i=cnt;i;i--)
for(int j=1;j<=26;j++)
if(ch[a[i]][j]) f[a[i]]+=f[ch[a[i]][j]]+1;
printf("%lld\n",f[1]);
}
}sam;
int main(){
sam.lst=sam.cnt=1; n=rd();
scanf("%s",s+1); sam.build();
sam.solve();
return 0;
}
求模式串在文本串中出现次数
要处理\(parent\)树上的倍增数组和\(right\)集合大小,然后扫描模式串,记录当前匹配了多少位,最后先判断匹配位数是否为\(m\)(m为模式串的长度)。是的话就倍增往上跳到满足\(len>=m\)的最高位置,把这个位置的\(right\)集合大小统计到答案中。
求\(a\)子串在\(b\)串中出现\(k\)次的出现次数
解题思路
首先构造广义\(sam\),然后对于每一个状态记录它属于哪个串,这个需要用\(set\)。如果一个状态属于串\(a\),那么其父节点也一定属于串\(a\),那么可以\(dfs\)启发式合并,把每个节点的\(set\)插到父节点。然后计算时要枚举每个串,同时在后缀自动机上走相应节点,然后判断\(set\)集合大小是否\(>k\),是的话统计答案,否则跳父节点,时间复杂度\(O(nlogn)\)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<set>
using namespace std;
const int N=100005;
const int M=200005;
typedef long long LL;
int n,k,head[M],tot,to[M<<1],nxt[M<<1],siz[M];
string s[N];
LL ans;
set<int> S[M];
struct SAM{
int fa[M],ch[M][28],len[M],cnt,lst;
void Insert(int c,int id){
int p=lst,np=++cnt; len[np]=len[p]+1;
lst=cnt; S[np].insert(id);
for(;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=1;
else {
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else {
int nq=++cnt; len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q]; fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
}sam;
inline void add(int bg,int ed){
to[++tot]=ed,nxt[tot]=head[bg],head[bg]=tot;
}
void dfs(int x){
for(int i=head[x];i;i=nxt[i]){
int u=to[i]; dfs(u); if(x==1) continue;
for(set<int>::iterator it=S[u].begin();it!=S[u].end();it++)
S[x].insert(*it);
}
siz[x]=S[x].size();
}
int main(){
scanf("%d%d",&n,&k); int len; sam.cnt=sam.lst=1;
for(int i=1;i<=n;i++){
cin>>s[i]; len=s[i].length(); sam.lst=1;
for(int j=0;j<len;j++) sam.Insert(s[i][j]-'a'+1,i);
}
if(k>n) {for(int i=1;i<=n;i++) puts("0"); return 0;}
for(int i=2;i<=sam.cnt;i++)
if(sam.fa[i]) add(sam.fa[i],i);
dfs(1); int p;
for(int i=1;i<=n;i++){
ans=0; len=s[i].length(); p=1;
for(int j=0;j<len;j++){
p=sam.ch[p][s[i][j]-'a'+1];
while(siz[p]<k) p=sam.fa[p];
ans+=sam.len[p];
}
printf("%lld\n",ans);
}
return 0;
}