1 条题解
-
0
暴力做法就是每次最坏 𝑂(𝑛) O(n) 地判断当前应该取首还是尾(即比较取首得到的字符串与取尾得到的反串的大小),只需优化这一判断过程即可.
由于需要在原串后缀与反串后缀构成的集合内比较大小,可以将反串拼接在原串后,并在中间加上一个没出现过的字符(如 #,代码中可以直接使用空字符),求后缀数组,即可 𝑂(1) O(1) 完成这一判断.
#include <cctype> #include <cstring> #include <iostream> using namespace std; constexpr int N = 1000010; char s[N]; int n, sa[N], id[N], oldrk[N * 2], rk[N * 2], px[N], cnt[N]; bool cmp(int x, int y, int w) { return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w]; } int main() { int i, w, m = 200, p, l = 1, r, tot = 0; cin >> n; r = n; for (i = 1; i <= n; ++i) while (cin >> s[i], !isalpha(s[i])); for (i = 1; i <= n; ++i) rk[i] = rk[2 * n + 2 - i] = s[i]; // 拼接正反两个字符串,中间空出一个字符 n = 2 * n + 1; // 求后缀数组 for (i = 1; i <= n; ++i) ++cnt[rk[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 *= 2, m = p) { // 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; memset(cnt, 0, sizeof(cnt)); 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]; memcpy(oldrk, rk, sizeof(rk)); for (p = 0, i = 1; i <= n; ++i) rk[sa[i]] = cmp(sa[i], sa[i - 1], w) ? p : ++p; } // 利用后缀数组O(1)进行判断 while (l <= r) { cout << (rk[l] < rk[n + 1 - r] ? s[l++] : s[r--]); if ((++tot) % 80 == 0) cout << '\n'; // 回车 } return 0; }
- 1
信息
- ID
- 1433
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 71
- 已通过
- 23
- 上传者