做法
对于字符串的第 个字符,定义对偶函数
,其含义为:对于原串的第
个字符,找到它之后与它相同的字符位置
,结果的第
个元素即为
。如果不存在这样的字符,相应的结果为
。
就像题中的函数 ,一个字符串的函数
就是由每个位置的
组成。
定义 上的小于关系为字典序从大到小,并且前缀优先。
举例:
按照对偶函数的顺序构造后缀数组即可。
举例:
以串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;
} 
京公网安备 11010502036488号