P5353 树上后缀排序
给出一棵 \(n\) 个节点,以 \(1\) 为根的树,点 \(i\) 上有字符 \(c_i\)。定义点 \(i\) 的字符串 \(s_i\) 为从点 \(i\) 走到点 \(1\) 路径上所有点上的字符拼接而成的字符串。
形式化的,若点 \(i\) 到点 \(1\) 的路径为 \(p_1,p_2,\dots ,p_k\,(p_1=i,p_k=1)\),则 \(s_i=\overline{c_{p_1}\dots c_{p_k}}\)。
你要对 \(1\sim n\) 这些点按照 \(s_i\) 的字典序进行排序,若字典序相同,则父亲排名小的点排名小。若仍相同,编号小的点排名小。
\(n\le 5\times 10^5\)。
看到对字符串排序想到后缀排序。我们可以类比后缀排序,利用倍增的思想,每次将两条长度为 \(2^i\) 的连续的、向上的链拼起来成为长度为 \(2^{i+1}\) 的链。所以要处理树上的倍增数组,记 \(fa_{i,u}\) 为点 \(u\) 向上走 \(2^i\) 条边到达的祖先。这部分的代码:
for (int l = 0, id; (1 << l) <= n; ++l) {
for (int i = 1; i <= n; ++i)
p[i] = {{rk[i], fa[l][i] ? rk[fa[l][i]] : 0}, i}; // 先比前半段,前半段相同再比后半段。
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) ++cnt[p[i].fi.se];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) tmp[cnt[p[i].fi.se]--] = p[i];
for (int i = 1; i <= n; ++i) p[i] = tmp[i];
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) ++cnt[p[i].fi.fi];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) tmp[cnt[p[i].fi.fi]--] = p[i];
for (int i = 1; i <= n; ++i) p[i] = tmp[i]; id = 0;
for (int i = 1; i <= n; ++i)
{ if (i == 1 || p[i].fi != p[i - 1].fi) ++id; rk[p[i].se] = id; }
if (id == n) { op = 1; break; }
}
关键是如何去重。这个代码求出来的 \(rk_i\) 表示将所有字符串去重后,\(s_i\) 的排名(排名定义为比它小的数的个数 \(+1\))。我们先通过以下代码求得不去重的排名:
memset(cnt, 0, sizeof cnt); for (int i = 1; i <= n; ++i) ++cnt[rk[i]];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= n; ++i) rk[i] = cnt[rk[i] - 1] + 1;
然后考虑两个字典序相同的字符串,它们的深度一定相同。因此用 vector 存放深度为 \(d\) 的点编号,然后从小往大处理,因为深度父亲的深度大于儿子。先将同一深度内的点按照 \(rk\) 排序。然后从头开始扫,扫出一段 \(\boldsymbol{rk}\) 相同的区间,然后再对这个区间内的点以父亲排名作为第一关键字、编号作为第二关键字排序。那么这些点的排名就是递增的,且第一个点的排名就是自己的 \(\boldsymbol {rk}\)。代码如下:
bool cmp1(int u, int v) { return rk[u] < rk[v]; }
bool cmp2(int u, int v) {
return rk[fa[0][u]] != rk[fa[0][v]] ? rk[fa[0][u]] < rk[fa[0][v]] : u < v;
}
for (int i = 0; i < n; ++i) {
sort(h[i].begin(), h[i].end(), cmp1);
for (int j = 0, k, l = h[i].size(), id; j < l; j = k) {
for (k = j; k < l && rk[h[i][k]] == rk[h[i][j]]; ++k);
sort(h[i].begin() + j, h[i].begin() + k, cmp2); id = rk[h[i][j]] - 1;
for (int d = j; d < k; ++d) rk[h[i][d]] = ++id;
}
}
去完重之后,如果用后缀排序中的名称来说,别忘记你输出的是 \(\boldsymbol{sa}\) 数组而不是 \(\boldsymbol{rk}\) 数组(有个人因此爆 \(0\) 了,我不说是谁),你要再做一遍双射。
现在来算时间复杂度,倍增处理 \(fa\) 数组以及倍增求 \(rk\) 都是 \(\mathcal{O}(n\log n)\) 的,至于去重,其实就是把“将长度为 \(n\) 的数组分成若干段,对每段分别进行排序并从左到右扫描”这个操作做了两次,根据乘法分配律和加法结合律,可以算出这部分的时间复杂度为 \(\mathcal{O}(n\log n)\)。
综上,本做法时间、空间复杂度均为 \(\mathcal{O}(n\log n)\)。
#include <bits/stdc++.h>
#define P pair
#define fi first
#define se second
using namespace std; const int N = 5e5 + 5;
typedef P<int, int> pii; typedef P<pii, int> ppi;
int n, fa[20][N], rk[N], sa[N], dep[N], cnt[N];
string s; vector<int> g[N], h[N]; ppi p[N], tmp[N]; bool op;
bool cmp1(int u, int v) { return rk[u] < rk[v]; }
bool cmp2(int u, int v) {
return rk[fa[0][u]] != rk[fa[0][v]] ? rk[fa[0][u]] < rk[fa[0][v]] : u < v;
}
void dfs(int u) {
for (int i = 1; (1 << i) <= dep[u]; ++i) fa[i][u] = fa[i - 1][fa[i - 1][u]];
for (int v : g[u]) dep[v] = dep[u] + 1, dfs(v); h[dep[u]].emplace_back(u);
}
void out() {
for (int i = 1; i <= n; ++i) sa[rk[i]] = i;
for (int i = 1; i <= n; ++i) cout << sa[i] << ' '; exit(0);
}
signed main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); cin >> n;
for (int i = 2, x; i <= n; ++i) cin >> x, g[fa[0][i] = x].emplace_back(i);
dfs(1); cin >> s; s = ' ' + s; for (int i = 1; i <= n; ++i) ++cnt[s[i]];
for (int i = 'a'; i <= 'z'; ++i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= n; ++i) rk[i] = cnt[s[i] - 1] + 1;
for (int l = 0, id; (1 << l) <= n; ++l) {
for (int i = 1; i <= n; ++i)
p[i] = {{rk[i], fa[l][i] ? rk[fa[l][i]] : 0}, i};
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) ++cnt[p[i].fi.se];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) tmp[cnt[p[i].fi.se]--] = p[i];
for (int i = 1; i <= n; ++i) p[i] = tmp[i];
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) ++cnt[p[i].fi.fi];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) tmp[cnt[p[i].fi.fi]--] = p[i];
for (int i = 1; i <= n; ++i) p[i] = tmp[i]; id = 0;
for (int i = 1; i <= n; ++i)
{ if (i == 1 || p[i].fi != p[i - 1].fi) ++id; rk[p[i].se] = id; }
if (id == n) { op = 1; break; }
}
if (op) out();
memset(cnt, 0, sizeof cnt); for (int i = 1; i <= n; ++i) ++cnt[rk[i]];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= n; ++i) rk[i] = cnt[rk[i] - 1] + 1;
for (int i = 0; i < n; ++i) {
sort(h[i].begin(), h[i].end(), cmp1);
for (int j = 0, k, l = h[i].size(), id; j < l; j = k) {
for (k = j; k < l && rk[h[i][k]] == rk[h[i][j]]; ++k);
sort(h[i].begin() + j, h[i].begin() + k, cmp2); id = rk[h[i][j]] - 1;
for (int d = j; d < k; ++d) rk[h[i][d]] = ++id;
}
}
out();
}

浙公网安备 33010602011771号