做法
对于字符串的第 个字符,定义对偶函数
,其含义为:对于原串的第
个字符,找到它之后与它相同的字符位置
,结果的第
个元素即为
。如果不存在这样的字符,相应的结果为
。
就像题中的函数 ,一个字符串的函数
就是由每个位置的
组成。
定义 上的小于关系为字典序从大到小,并且前缀优先。
举例:
按照对偶函数的顺序构造后缀数组即可。
举例:
以串abaabaaaabba
为例:
a ba baaaabba abba baabaaaabba abaaaabba abaabaaaabba bba aabba aabaaaabba aaabba aaaabba 答案即为列 。
证明
详细证明见论文 Parameterized Suffix Arrays for Binary Strings。以下大致描述该论文的思路。
我们只需证明, 与
存在对应的偏序关系。即
那么 时显然。下面考虑小于的情况。
如果
是
的前缀,那么
要么是
的前缀,要么把所有
中的
互相代换之后是
的前缀。反之亦然。
证明:
反证。不妨设
那么中必然有一个为
,
中必然没有
。
与 「是
的前缀」相矛盾。
另一侧的证明显然。由此,显然对于任意的
,要么
,要么
。即
要么是
的前缀,要么存在
。
如果
不是
的前缀。那么必然存在
。
记满足的串为
,满足
的串为
。
存在如下没有前缀关系的情况(以下假设):
与
pos 1 2 i-1 i i+1 0 1 1 1 0 0 1 1 1 1 1 1 1 sth>1 or sth 1 1 1 1 1 or sth 可知
与
pos 1 2 ... i-1 i i+1 0 1 ... 1 1 0 0 1 ... 1 1 1 1 1 ... 1 sth>1 or sth 1 1 ... 1 1 1 or x 可知
综上,原命题得证。
时间复杂度
视后缀数组的构造算法, 到
实现细节
通过令 ,并且加入虚节点
的方式,通过普通后缀数组模板实现排序。
代码
#include <bits/stdc++.h> using namespace std; const int N=3000020; char ts[N]; int s[N]; int n,sa[N],rk[N],oldrk[N << 1],id[N],px[N],cnt[N]; bool cmp(int x,int y,int w) { return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w]; } void solve() { int l[2]={n+1,n+1}; for(int i=n;i>0;i--) { s[i]=((l[ts[i]-'a']>n?n:l[ts[i]-'a']-i)); l[ts[i]-'a']=i; } n++; s[n]=n; int i,m=n+3,p,w; for(int i=0;i<=n+5;i++) { sa[i]=rk[i]=oldrk[i]=oldrk[i+n+5]=id[i]=px[i]=cnt[i]=0; } for(i=1;i<=n;++i) ++cnt[rk[i]=s[i]]; for(i=1;i<=m;++i) cnt[i]+=cnt[i-1]; for(i=n;i>=1;--i) sa[cnt[rk[i]]--]=i; for(w=1;w<n;w<<=1,m=p) { for(p=0,i=n;i>n-w;--i) id[++p]=i; for(i=1;i<=n;++i) if(sa[i]>w) id[++p]=sa[i]-w; for(i=0;i<=n;++i) cnt[i]=0; for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]]; for(i=1;i<=m;++i) cnt[i]+=cnt[i-1]; for(i=n;i>=1;--i) sa[cnt[px[i]]--]=id[i]; for(i=0;i<=min(N-1,2*n+5);++i) oldrk[i]=rk[i]; for(p=0,i=1;i<=n;++i) rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p; } for(i=n-1;i>=1;--i) printf("%d ",sa[i]); printf("\n"); } int main(void) { while(~scanf("%d",&n)) { scanf("%s",ts+1); solve(); } return 0; }