voidsuffix_sort(std::string s, int *sa, int *rk){ staticint cnt[kMaxN], ork[kMaxN << 1], id[kMaxN]; memset(cnt, 0, sizeof(cnt)); int n = static_cast<int>(s.size()) - 1; for (int i = 1; i <= n; ++i) { rk[i] = s[i]; ++cnt[rk[i]]; } for (int i = 1; i <= 128; ++i) cnt[i] += cnt[i - 1]; for (int i = n; i; --i) sa[cnt[rk[i]]--] = i; for (int i = 1; i <= n; ++i) ork[i] = rk[i]; int m = 0; for (int i = 1; i <= n; ++i) { if (ork[sa[i]] == ork[sa[i - 1]]) { rk[sa[i]] = m; } else { rk[sa[i]] = ++m; } } for (int w = 1; w < n; w <<= 1) { memset(cnt, 0, sizeof(cnt)); for (int i = 1; i <= n; ++i) id[i] = sa[i]; for (int i = 1; i <= n; ++i) ++cnt[rk[id[i] + w]]; for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1]; for (int i = n; i; --i) sa[cnt[rk[id[i] + w]]--] = id[i];
memset(cnt, 0, sizeof(cnt)); for (int i = 1; i <= n; ++i) id[i] = sa[i]; for (int i = 1; i <= n; ++i) ++cnt[rk[id[i]]]; for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1]; for (int i = n; i; --i) sa[cnt[rk[id[i]]]--] = id[i];
for (int i = 1; i <= n; ++i) ork[i] = rk[i]; m = 0; for (int i = 1; i <= n; ++i) { if (ork[sa[i]] == ork[sa[i - 1]] && ork[sa[i] + w] == ork[sa[i - 1] + w]) { rk[sa[i]] = m; } else { rk[sa[i]] = ++m; } } } }
但是这么做常数很大,并且实测还没直接 sort 快,因为做两次基数排序是很慢的。
注意到第一次排序相当于把 i+w>n 的 i 放前面,并且把其他的 i 按照 rk[i+w] 的顺序排序。
又因为原来的 sa 数组就已经按照 rk 排好序了,所以直接从前往后扫 sa 数组,如果 sa[i]>w 就把 sa[i]−w 放到新数组中即可。
voidgetsa(std::string s, int *sa, int *rk){ staticint cnt[kMaxN], ork[kMaxN << 1], id[kMaxN]; memset(cnt, 0, sizeof(cnt)); int n = static_cast<int>(s.size()) - 1, m = 0; for (int i = 1; i <= n; ++i) { rk[i] = s[i]; ++cnt[rk[i]]; } for (int i = 1; i <= 128; ++i) cnt[i] += cnt[i - 1]; for (int i = n; i; --i) sa[cnt[rk[i]]--] = i; std::copy_n(rk + 1, n, ork + 1); for (int i = 1; i <= n; ++i) { if (ork[sa[i]] == ork[sa[i - 1]]) { rk[sa[i]] = m; } else { rk[sa[i]] = ++m; } } for (int w = 1; m < n; w <<= 1) { int p = 0; for (int i = n - w + 1; i <= n; ++i) id[++p] = i; for (int i = 1; i <= n; ++i) if (sa[i] > w) id[++p] = sa[i] - w; std::fill_n(cnt + 1, n, 0); for (int i = 1; i <= n; ++i) ++cnt[rk[id[i]]]; for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1]; for (int i = n; i; --i) sa[cnt[rk[id[i]]]--] = id[i];
m = 0; std::copy_n(rk + 1, n, ork + 1); for (int i = 1; i <= n; ++i) { if (ork[sa[i]] == ork[sa[i - 1]] && ork[sa[i] + w] == ork[sa[i - 1] + w]) { rk[sa[i]] = m; } else { rk[sa[i]] = ++m; } } } }
height 数组
定义:sa[i−1] 和 sa[i] 的最长公共前缀长度。
求 height
引理:height[rk[i]]≥height[rk[i−1]]−1。
证明
height[rk[i]] 就是 si 与 sa 中 i 前面的后缀的 LCP,height[rk[i−1]] 是 si−1 与 sa 中 i−1 前面的后缀的 LCP。
又因为 i−1 在 sa 前面的那个后缀删掉最前面的字符还是一个后缀,所以 i 与其它后缀的最大 LCP ≥height[rk[i−1]]−1,感性理解一下这是对的。
于是按照这个引理求就可以做到 O(n)。
代码
1 2 3 4 5 6 7 8
voidgetheight(std::string s, int *height){ int n = static_cast<int>(s.size()) - 1; for (int i = 1, p = 0; i <= n; ++i) { if (p) --p; for (; i + p <= n && sa[rk[i] - 1] + p <= n && s[i + p] == s[sa[rk[i] - 1] + p]; ++p) {} height[rk[i]] = p; } }