?】一些定义:(x)
字符Ԍ(x)q义的字W串是指“元素cd有序Q且元素值有一定范围的序列”Q其元素不一定非要是字符Q可以是数字{,因此整数、二q制数等也是字符Ԍ
字符集:(x)字符串的元素值的范围UCؓ(f)字符集,其大记为SZ?br />字符串的长度Q字W串中元素的个数Q一般记为NQ长度ؓ(f)N的字W串AW一ơ提到时一般用A[0..N-1]来表C;
前缀Q字W串A[0..N-1]的从A[0]开始的若干个连l的字符l成的字W串UCؓ(f)A的前~Q以?#8220;前缀i”或?#8220;~号为i的前~”指的都是A[0..i]Q?br />后缀Q字W串A[0..N-1]的到A[N-1]l止的若q个q箋的字W组成的字符串称为A的后~Q以?#8220;后缀i”或?#8220;~号为i的后~”指的都是A[i..N-1];
对于一个长度ؓ(f)N的字W串Q将其N个后~按字典序大小q行排序Q得C个数lsa[i]和rank[i]Qsa[i]为排在第i位的后缀的编P也就是一般说的ord[i]Q,rank[i]为排在后~i排在的位|(UCؓ(f)后缀i的名ơ)。sa、rank值的范围均ؓ(f)[0..N-1]。sa和rank互逆,即sa[i]=j{h(hun)于rank[j]=iQ或者说成sa[rank[i]]=rank[sa[i]]=i。这里,saUCؓ(f)后缀数组QrankUCؓ(f)名次数组?br />
?】用倍增法求后~数组Q?br />在论文里Q后~数组有两U求法:(x)倍增法和DC3法Q前者的旉复杂度ؓ(f)O(NlogN)Q但常数较小Q后者的旉复杂度ؓ(f)O(N)Q但常数较大Q在实际应用中,两者的L间相差不大,且后者比前者难理解得多Q本沙茶理解前者都用了几天旉……后者就木敢看了Q。这里就ȝ一下倍增法吧囧……
首先Q脓(chung)一下本沙茶的用倍增法求后~数组的模板:(x)
void suffix_array()
{
int p, v0, v1, v00, v01;
re(i, SZ) S[i] = 0;
re(i, n) rank[i] = A[i];
re(i, n) S[A[i]]++;
re2(i, 1, SZ) S[i] += S[i - 1];
rre(i, n) sa[--S[A[i]]] = i;
for (int j=1; j<n; j<<=1) {
p = 0; re2(i, n-j, n) tmp[p++] = i;
re(i, n) if (sa[i] >= j) tmp[p++] = sa[i] - j;
re(i, SZ) S[i] = 0;
re(i, n) S[rank[i]]++;
re2(i, 1, SZ) S[i] += S[i - 1];
rre(i, n) sa[--S[rank[tmp[i]]]] = tmp[i];
tmp[sa[0]] = p = 0;
re2(i, 1, n) {
v0 = sa[i - 1]; v1 = sa[i];
if (v0 + j < n) v00 = rank[v0 + j]; else v00 = -1;
if (v1 + j < n) v01 = rank[v1 + j]; else v01 = -1;
if (rank[v0] == rank[v1] && v00 == v01) tmp[sa[i]] = p; else tmp[sa[i]] = ++p;
}
re(i, n) rank[i] = tmp[i];
SZ = ++p;
}
}
q里A是待求sa和rank的字W串?br />
<1>倍增法的思想Q?br />记R[i][j]为A[i..i+2j-1]Q如果越界,则后面用@填充Q在A的所有长度ؓ(f)2j的子Ԍ界则后面用@填充Q中的名ơ(rankQ倹{倍增法是按阶D|出所有R[i][j]的|直到2j>N为止。首先,R[i][0]的就是字WA[i]在A[0..N-1]中的名次Q是可以直接用计数排序来实现的。然后,若R[0..N-1][j-1]已知Q则可以按照以下Ҏ(gu)求出R[0..N-1][j]的|(x)Ҏ(gu)个iQ?<=i<NQ,构造一个二元组<Xi, Yi>Q其中Xi=R[i][j-1]QYi=R[i+2j][j-1]Q若i+2j>=NQ则Yi=-∞Q,然后对这N个二元组按照W一关键字ؓ(f)XQ第二关键字为YQ若两者都相等则判定ؓ(f)相等Q进行排序(可以用基数排序来实现Q,排序后,<Xi, Yi>的名ơ就是的R[i][j]的倹{?br />
<2>一开始,对A中的各个字符q行计数排序Q?
re(i, SZ) S[i] = 0;
re(i, n) rank[i] = A[i];
re(i, n) S[A[i]]++;
re2(i, 1, SZ) S[i] += S[i - 1];
rre(i, n) sa[--S[A[i]]] = i;
q个木有马好说的,在搞懂了基数排序之后可以U掉。唯一不同的是q里加了一句:(x)rank[i]=A[i]Q这里的rank[i]是初始的i的名ơ,MS不符合rank[i]的定义和sa与rank间的互逆性。这里就要解释一下了囧。因为在求sa的过E中Qrank值可能不W合定义Q因为长度ؓ(f)2j的子串可能会(x)有相{的Q此时它们的rankg要相{,而sa值由于有下标的限制所以不可能有相{的。因此,在过E中Qrank其实是用来代替A的子串的Q这样rank值只需要表CZ?#8220;相对序”p了,也就是:(x)rank[i0]>(=, <)rank[i1]Q当且仅当A[i0..i0+2j-1]>(=, <)A[i1..i1+2j-1]。这P可以直接A[i]g为初始的rank[i]倹{?br />
<3>jQ代?jQ的g1开始不断倍增Q对二元l进行基数排序求出新阶段的sa|(x)
for (int j=1; j<n; j<<=1) {
p = 0; re2(i, n-j, n) tmp[p++] = i;
re(i, n) if (sa[i] >= j) tmp[p++] = sa[i] - j;
re(i, SZ) S[i] = 0;
re(i, n) S[rank[i]]++;
re2(i, 1, SZ) S[i] += S[i - 1];
rre(i, n) sa[--S[rank[tmp[i]]]] = tmp[i];
注意q个基数排序的过E是很特别的。首先,它ƈ不是对A在进行排序,而是对上一阶段求出的rank在进行排序。因为前面已l说q,在求sa的过E中Qrank是用来代替A的对应长度的子串的,׃不能直接对子串进行排序(那样的话旉开销很恐怖的Q,所以只能对rankq行排序。另外,q里在对二元l?lt;x, y>的第二关键字QyQ进行排序的q程中加了优化:(x)q些y其实是把上一阶段的sa整体左移了jQ右边空出的部分全部用@Q空Ԍ填充得到的,׃IZ的字典序肯定最,因此右边的IZ按照下标序先写入(f)时saQ代码中用tmp表示的就是(f)时saQ也是对第二关键字y排序后的ordl果Q,然后Q上一阶段的sa如果左移后还木有消失的(也就是sa值大于等于j的)Q再按顺序写入(f)时saQ就得到了排序结果。剩下的对x的排序结果就是上一阶段的saQ唯一不同的是对于x相同的,按照临时名次递增的顺序?br />
<4>求出新阶D늚rank|(x)
tmp[sa[0]] = p = 0;
re2(i, 1, n) {
v0 = sa[i - 1]; v1 = sa[i];
if (v0 + j < n) v00 = rank[v0 + j]; else v00 = -1;
if (v1 + j < n) v01 = rank[v1 + j]; else v01 = -1;
if (rank[v0] == rank[v1] && v00 == v01) tmp[sa[i]] = p; else tmp[sa[i]] = ++p;
}
re(i, n) rank[i] = tmp[i];
SZ = ++p;
׃下一阶段需要用本阶段的rank|因此在求Z本阶D늚sag后,需要求rank倹{(代码中的tmp起了临时rank的作用,目的是节省空_(d)
因ؓ(f)sa值已l求出,因此只要依次扫描sa可以得到rank|唯一要做的工作就是找到哪些子串是相等的,它们的rank值应该相{,除此之外Qrank值只要依ơ加1卛_。判定相{的Ҏ(gu)Q只需判定rank[i]和rank[i+j]是否都对应相{即可。若rank[i+j]界Q用-∞Q当然Q何一个负数都行,代码中用?1Q来表示?br />最后还有一个优化:(x)׃本阶D늚名次的范围只有[0..p]q么多,下一阶段?#8220;字符?#8221;Q其实就是rank集)的大SZ可以设ؓ(f)p+1Q这样可以省一些时间?br />
q样后缀数组sa和名ơ数lrank全部求完了?br />
以后q有一些更重要的东东就是AC自动机、后~数组{的应用问题Q算了,以后再搞吧囧?br />
]]>