题解 P3809 【【模板】后缀排序】

teafrogsf

2019-03-20 20:32:21

Solution

咋网上用$SAM$建$SA$的题解这么少啊$\cdots\cdots$这又不是啥理性愉悦的东西。 这是一种$O(n)$构建$SA$的方法。常数未与$DC3,SA-IS$对比,但代码难度小于$DC3,SA-IS$与倍增(如果你更熟练写$SAM$)。当然因实际实现的限制,时间复杂度是$O(n|S|)$的,其中$S$是字符集。 首先我们可以发现,反串的$parent$树就是原串的后缀树。 那么有了后缀树,我们怎么求后缀数组呢? 我们直接按字典序$dfs$后缀树,如果遇到了一个合法后缀$x$,那么结点$x$的$sa(x)$就是它的$dfn$。 至于怎么按字典序有一点细节。 实际上,你需要注意后缀树与反串$parent$树虽然形态一模一样,但是你后缀树的一条转移边上的串其实是反串$parent$**树上每个节点代表的串的一部分,而且串的形态是正串的形态而不是反串。因此你不能直接在构造**$SAM$**时求出一个节点的字典,而是需要一些操作。** 具体来说,我们在构建反串$SAM$时记录一下$cur$的$right$**在原串中的位置**,然后我们考虑$link(u)\to u$这条边表示的串实际上是**这个结点表示的最长串的反串去掉**$link(u)$**中已有的部分。** 那我们把上面的位置求出来,每次建后缀树边的时候就可以算出这条边的字典了。 当然本题字符集大小为$62$,因此只能套上一个$map/tree$,时间复杂度退化~~进化~~为$O(n\log|S|)$~~,然而常数不见得就比倍增优秀。~~ ```cpp //显然这份代码无法通过此题。放上来仅作为参考。 #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define f(i,a,b) for(register int i=(a),i##end=(b);i<=i##end;i=-(~i)) const int neko=100010,seko=200010; int n,sa[neko],rk[neko],height[neko]; typedef int arr[seko]; arr link,len,pos,suf; int nex[seko][26],G[seko][26]; char s[neko]; namespace SAM { int las=1,cur,cnt=1,tp=0; void extend(char *s) { int x,p,q,clone; link[1]=-1; f(i,1,n) { x=s[i]-'a',cur=++cnt; len[cur]=len[las]+1,pos[cur]=n-i+1,suf[cur]=1; //printf("qwq %d %d %d\n",cur,pos[cur],col[cur]); for(p=las;p!=-1&&!nex[p][x];p=link[p])nex[p][x]=cur; if(p==-1)link[cur]=1; else { q=nex[p][x]; if(len[q]==len[p]+1)link[cur]=q; else { clone=++cnt; len[clone]=len[p]+1,link[clone]=link[q],pos[clone]=pos[q]; memcpy(nex[clone],nex[q],sizeof nex[q]); for(;p!=-1&&nex[p][x]==q;p=link[p])nex[p][x]=clone; link[cur]=link[q]=clone; } }las=cur; } } void dfs(int u) { if(suf[u])++tp,rk[sa[tp]=pos[u]]=tp; //printf("orz %d %d %d\n",u,link[u],pos[u]); for(int v:G[u])if(v)dfs(v); } } namespace SA { using namespace SAM; void getsa(char *s) { std::reverse(s+1,s+n+1),extend(s),std::reverse(s+1,s+n+1); f(i,2,cnt)G[link[i]][s[pos[i]+len[link[i]]]-'a']=i; dfs(1); } void getheight(char *s) { int ans=0,x; f(i,1,n) { if(ans)--ans; x=sa[rk[i]-1]; if(x) f(j,ans,n-i+1)if(s[x+j]!=s[i+j]) {height[rk[i]]=ans=j;break;} //printf("%d %d %d\n",x,i,ans); } } } int main() { freopen("SA.in","r",stdin); freopen("SA.out","w",stdout); using namespace SA; scanf("%s",s+1),n=strlen(s+1); getsa(s),getheight(s); f(i,1,n)printf("%d%c",sa[i],i^iend?' ':'\n'); f(i,2,n)printf("%d%c",height[i],i^iend?' ':'\n'); return 0; } ``` $P.S.$ https://www.luogu.org/blog/teafrogsf/post-LSTAM https://www.luogu.org/blog/teafrogsf/post-SA 欢迎来我的博客找到有关$SAM,SA$的详细内容。