广义SAM用来处理多个字符串的子串的问题。一般是先建trie,再在trie上建SAM。也可以不建trie直接每次插一个串,每次需要把lst设为S。
复杂度是O(trie树节点数)的。所以一般复杂度和加特殊字符拼接好像没什么区别。
不过如果trie树是题目给你的那就可以放心用了。
在trie上建SAM的方法是插入p节点时先把lst置为父亲所在状态,好像挺简单的。若插入字符时当前节点已有这个出边,要像q一样判一下mxlen,具体看代码。
例题:[ZJOI2015]诸神眷顾的幻想乡
给你一棵字符构成的树,保证叶子节点不超过20个,问树上所有简单路径构成不同的字符串有多少。
这个就是广义SAM的裸题了。考虑叶子节点少,于是我们可以分别把这些叶子当根建SAM即可。
对于在trie上建SAM还有一个小问题,就是遍历trie的方式。有bfs和dfs两种,那么选哪种呢?事实上,dfs是可以被卡成的。
数据生成器:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5;
int n=N-1,m=N>>1;
int main(){
printf("%d\n",n);
for(int i=1;i<=m;i++)printf("0 ");
for(int i=m+1;i<=n;i++)printf("1 ");
puts("");
for(int i=m+1;i<=n;i++)printf("%d %d\n",n-i+1,i);
for(int i=2;i<=m;i++)printf("%d %d\n",i-1,i);
return 0;
}
但是对于这道题目,dfs的遍历方式是可以A掉的,叶子数的限制使得他无法构造这样的数据。
贴上bfs的代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e6+50;
int n,c,a[N],ver[N],nxt[N],head[N],tot,d[N],p[N],link[N],trans[N][10],mxlen[N],cnt=1,lst=1,q[N],l,r,f[N];LL ans;
int read(){
int x=0,c;
while(!isdigit(c=getchar()));
while(isdigit(c))x=x*10+(c^48),c=getchar();
return x;
}
void add(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;d[y]++;}
void extend(int c){
int cur=0,p=lst;
if(!trans[p][c])lst=cur=++cnt,mxlen[cur]=mxlen[p]+1;
for(;p&&!trans[p][c];p=link[p])trans[p][c]=cur;
if(!p)link[cur]=1;
else{
int q=trans[p][c];
if(mxlen[q]==mxlen[p]+1){cur?link[cur]=q:lst=q;}
else{
int nq=++cnt;cur?0:lst=nq;
memcpy(trans[nq],trans[q],sizeof(trans[q]));
mxlen[nq]=mxlen[p]+1;link[nq]=link[q];
for(;p&&trans[p][c]==q;p=link[p])trans[p][c]=nq;
link[q]=nq;if(cur)link[cur]=nq;
}
}
}
void bfs(int x){
q[l=r=1]=x;f[x]=0;
while(l<=r){
int x=q[l++];
lst=p[f[x]];extend(a[x]);p[x]=lst;
for(int i=head[x],y;i;i=nxt[i])if((y=ver[i])^f[x])f[y]=x,q[++r]=y;
}
}
int main(){
scanf("%d%d",&n,&c);p[0]=1;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,x,y;i<n;i++)x=read(),y=read(),add(x,y),add(y,x);
for(int i=1;i<=n;i++)if(d[i]==1)bfs(i);
for(int i=2;i<=cnt;i++)ans+=mxlen[i]-mxlen[link[i]];
printf("%lld\n",ans);
return 0;
}
完结撒花!
下面还有一些要记录的性质:
一个串的所有子串在广义后缀自动机中所在的状态量仍然是O(len)级别的(ztb说可以卡,hack咕咕咕),可以通过打标记暴力跳父亲的方式不重不漏地访问到。看一眼代码:
for(int i=1;i<=n;i++)for(int j=1,p=1;j<=len[i];j++){
p=trans[p][s[i][j]-'a'];
for(int l=p;l;l=par[l])
if(flag[l]^i)flag[l]=i,v[i].push_back(l);
else break;
}
我们来hack它233,想要hack的话就是让状态量达到级别,那么有一种方法是先造一个主串,输出它所有的子串。于是所有的状态都会裂开,对于每个串的状态量都达到了级别。
那么来分析一下复杂度:设主串长度为n,则输入总长达到级别,算法复杂度是级别。那么关于总长的复杂度就是。还是很优秀嘛,不知道还有没有什么更优秀的hack。咕咕咕
这是我随便rand的一个长度为200的主串的结果,第一个输出是总串长,第二个是访问到的总状态数。应该说的确到达了这个复杂度。
据说这样复杂度是的qwq