[BZOJ 1692] [Usaco2007 Dec] 队列变换 【后缀数组 + 贪心】
---恢复内容开始---
题目链接:BZOJ - 1692
题目分析
首先,有个比较简单的贪心思路:如果当前剩余字符串的两端字母不同,就选取小的字母,这样显然是正确的。
然而若两端字母相同,我们怎么选取呢?
这时我们要从两端分别向内部比较,看那一端向内的字符串字典序小。
比如这个字符串 ABCDBA,从左端向内是 ABC.. 从右端向内是 ABD... 所以就选取左端的字符。
这样直接比较是 O(n^2) 的,我们可以使用后缀数组的 Rank 数组来比较。
我们在字符串后加上分隔符,然后再将字符串反转接在后面,求后缀数组的 Rank 数组。
这样就可以快速比较一个前缀,一个后缀的字典序大小了,具体见代码。
比如 ABCDBA ,就存成 ABCDBA#ABDCBA 。
代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const int MaxL = 60000 + 15; int n; int A[MaxL], Rank[MaxL], SA[MaxL]; int VA[MaxL], VB[MaxL], VC[MaxL], Sum[MaxL]; char S[MaxL], Sout[MaxL]; inline bool Cmp(int *a, int x, int y, int l) { return (a[x] == a[y]) && (a[x + l] == a[y + l]); } void DA(int *A, int n, int m) { int *x, *y, *t; x = VA; y = VB; for (int i = 1; i <= m; ++i) Sum[i] = 0; for (int i = 1; i <= n; ++i) ++Sum[x[i] = A[i]]; for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1]; for (int i = n; i >= 1; --i) SA[Sum[x[i]]--] = i; int p, q; p = 0; for (int j = 1; p < n; j <<= 1, m = p) { q = 0; for (int i = n - j + 1; i <= n; ++i) y[++q] = i; for (int i = 1; i <= n; ++i) { if (SA[i] <= j) continue; y[++q] = SA[i] - j; } for (int i = 1; i <= n; ++i) VC[i] = x[y[i]]; for (int i = 1; i <= m; ++i) Sum[i] = 0; for (int i = 1; i <= n; ++i) ++Sum[VC[i]]; for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1]; for (int i = n; i >= 1; --i) SA[Sum[VC[i]]--] = y[i]; t = x; x = y; y = t; x[SA[1]] = 1; p = 1; for (int i = 2; i <= n; ++i) x[SA[i]] = Cmp(y, SA[i], SA[i - 1], j) ? p : ++p; } for (int i = 1; i <= n; ++i) Rank[SA[i]] = i; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { cin >> S[i]; A[i] = S[i] - 'A' + 1; A[2 * n - i + 2] = A[i]; } A[n + 1] = 27; A[n * 2 + 2] = 28; DA(A, n * 2 + 2, 28); int l = 1, r = n, Top = 0; while (l <= r) { if (S[l] != S[r]) { if (S[l] < S[r]) { Sout[++Top] = S[l]; ++l; } else { Sout[++Top] = S[r]; --r; } continue; } if (l == r) { Sout[++Top] = S[l]; break; } if (Rank[l] < Rank[n * 2 - r + 2]) { Sout[++Top] = S[l]; ++l; } else { Sout[++Top] = S[r]; --r; } } for (int i = 1; i <= Top; ++i) { printf("%c", Sout[i]); if (i % 80 == 0) printf("\n"); } return 0; }
---恢复内容结束---