hdu 5442 (ACM-ICPC2015长春网络赛F题)
题意:给出一个字符串,长度是2*10^4。将它首尾相接形成环,并在环上找一个起始点顺时针或逆时针走一圈,求字典序最大的走法,如果有多个答案则找起始点最小的,若起始点也相同则选择顺时针。
分析:后缀数组。
首先,需要补充后缀数组的基本知识的话,看这个链接。
http://www.cnblogs.com/rainydays/archive/2011/05/09/2040993.html
我利用这道题重新整理了后缀数组的模板如下:
const int MAX_LEN = 0; //call init_RMQ(f[], n) first. //then call query(a, b) to quest the RMQ of [a, b]. int power[30]; int st[MAX_LEN * 2][32]; int ln[MAX_LEN * 2]; //returns the index of the first minimum value in [x, y] void init_RMQ(int f[], int n) { int i, j; for (power[0] = 1, i = 1; i < 21; i++) { power[i] = 2 * power[i - 1]; } for (i = 0; i < n; i++) { st[i][0] = i; } ln[0] = -1; for (int i = 1; i <= n; i++) { ln[i] = ln[i >> 1] + 1; } for (j = 1; j < ln[n]; j++) { for (i = 0; i < n; i++) { if (i + power[j - 1] - 1 >= n) { break; } //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (f[st[i][j - 1]] > f[st[i + power[j - 1]][j - 1]]) { st[i][j] = st[i + power[j - 1]][j - 1]; } else { st[i][j] = st[i][j - 1]; } } } } int query(int f[], int x, int y) { if(x > y) { swap(x, y); } int k = ln[y - x + 1]; //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (f[st[x][k]] > f[st[y - power[k] + 1][k]]) return st[y - power[k] + 1][k]; return st[x][k]; } //first use the constructed function //call function lcp(l, r) to get the longest common prefix of sa[l] and sa[r] //have access to the member of sa, myrank, height and so on //height is the LCP of sa[i] and sa[i + 1] class SuffixArray { public: char* s; int n, sa[MAX_LEN], height[MAX_LEN], myrank[MAX_LEN]; int tmp[MAX_LEN], top[MAX_LEN]; SuffixArray() {} //the string and the length of the string SuffixArray(char* st, int len) { s = st; n = len + 1; make_sa(); make_lcp(); } void make_sa() { // O(N * log N) int na = (n < 256 ? 256 : n); memset(top, 0, na * sizeof(int)); for (int i = 0; i < n ; i++) top[myrank[i] = s[i] & 0xff]++; for (int i = 1; i < na; i++) top[i] += top[i - 1]; for (int i = 0; i < n ; i++) sa[--top[ myrank[i]]] = i; int x; for (int len = 1; len < n; len <<= 1) { for (int i = 0; i < n; i++) { x = sa[i] - len; if (x < 0) x += n; tmp[top[myrank[x]]++] = x; } sa[tmp[top[0] = 0]] = x = 0; for (int i = 1; i < n; i++) { if (myrank[tmp[i]] != myrank[tmp[i-1]] || myrank[tmp[i]+len]!=myrank[tmp[i-1]+len]) top[++x] = i; sa[tmp[i]] = x; } memcpy(myrank, sa , n * sizeof(int)); memcpy(sa , tmp, n * sizeof(int)); if (x >= n - 1) break; } } void make_lcp() { // O(4 * N) int i, j, k; for (j = myrank[height[i = k = 0] = 0]; i < n - 1; i++, k++) { while (k >= 0 && s[i] != s[sa[j - 1] + k]) { height[j - 1] = (k--); j = myrank[sa[j] + 1]; } } init_RMQ(height, n - 1); } int lcp(int l, int r) { return height[query(height, l, r - 1)]; } };
先将两个原字符串拼成一个长度是2倍的字符串,便于处理越过原串末尾的情况。
运行后缀数组求顺时针解。
反转这个长字符串,再运行后缀数组,求逆时针解。
比较两个解,得出最终解。
下面我们来分析一下,每次运行后缀数组之后是如何求解的。
设原串长度为len。
首先从sa[len*2]开始依次向前找。即从排序最大的开始向前找。之所以排序最大的不一定是我们要的答案是因为:
1.它可能起始点在后面的附加的串上,并没有构成完整的一圈。
2.我们还要找起始点最小的答案呢。(当然,对于反转后的字符串,我们要找起始点最大的)
在第一次遇到一个起始点小于len的时候,表明这个可能就是答案。但是前面可能有起始点更小的,我们还要继续沿着sa向前找,但找的时候一定要保证字典序是最大的,所以每次向前移动一个,都要观察height(i, i+1)是否>len,如果不是,则说明字典序已经不是最大了。
特别注意,height==len的情况也是不可以的,因为两者相等很有可能表明其中一个的起始点已经等于len。当然也可以对每个都判断一下起始点位置。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define d(x) const int MAX_N = (int)(4e4) + 100; //call init_RMQ(f[], n) first. //then call query(a, b) to quest the RMQ of [a, b]. int power[30]; int st[MAX_N * 2][32]; int ln[MAX_N * 2]; //returns the index of the first minimum value in [x, y] void init_RMQ(int f[], int n) { int i, j; for (power[0] = 1, i = 1; i < 21; i++) { power[i] = 2 * power[i - 1]; } for (i = 0; i < n; i++) { st[i][0] = i; } ln[0] = -1; for (int i = 1; i <= n; i++) { ln[i] = ln[i >> 1] + 1; } for (j = 1; j < ln[n]; j++) { for (i = 0; i < n; i++) { if (i + power[j - 1] - 1 >= n) { break; } //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (f[st[i][j - 1]] > f[st[i + power[j - 1]][j - 1]]) { st[i][j] = st[i + power[j - 1]][j - 1]; } else { st[i][j] = st[i][j - 1]; } } } } int query(int f[], int x, int y) { if(x > y) { swap(x, y); } int k = ln[y - x + 1]; //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (f[st[x][k]] > f[st[y - power[k] + 1][k]]) return st[y - power[k] + 1][k]; return st[x][k]; } //first use the constructed function //call function lcp(l, r) to get the longest common prefix of sa[l] and sa[r] //have access to the member of sa, myrank, height and so on //height is the LCP of sa[i] and sa[i + 1] class SuffixArray { public: char* s; int n, sa[MAX_N], height[MAX_N], myrank[MAX_N]; int tmp[MAX_N], top[MAX_N]; SuffixArray() {} //the string and the length of the string SuffixArray(char* st, int len) { s = st; n = len + 1; make_sa(); make_lcp(); } void make_sa() { // O(N * log N) int na = (n < 256 ? 256 : n); memset(top, 0, na * sizeof(int)); for (int i = 0; i < n ; i++) top[myrank[i] = s[i] & 0xff]++; for (int i = 1; i < na; i++) top[i] += top[i - 1]; for (int i = 0; i < n ; i++) sa[--top[ myrank[i]]] = i; int x; for (int len = 1; len < n; len <<= 1) { for (int i = 0; i < n; i++) { x = sa[i] - len; if (x < 0) x += n; tmp[top[myrank[x]]++] = x; } sa[tmp[top[0] = 0]] = x = 0; for (int i = 1; i < n; i++) { if (myrank[tmp[i]] != myrank[tmp[i-1]] || myrank[tmp[i]+len]!=myrank[tmp[i-1]+len]) top[++x] = i; sa[tmp[i]] = x; } memcpy(myrank, sa , n * sizeof(int)); memcpy(sa , tmp, n * sizeof(int)); if (x >= n - 1) break; } } void make_lcp() { // O(4 * N) int i, j, k; for (j = myrank[height[i = k = 0] = 0]; i < n - 1; i++, k++) { while (k >= 0 && s[i] != s[sa[j - 1] + k]) { height[j - 1] = (k--); j = myrank[sa[j] + 1]; } } init_RMQ(height, n - 1); } int lcp(int l, int r) { return height[query(height, l, r - 1)]; } }; SuffixArray suffix; int len; char ans1[MAX_N], ans2[MAX_N]; int pos1, pos2; char s[MAX_N]; int work1(char* ans) { int p = len * 2; while (suffix.sa[p] >= len) p--; for (int i = p - 1; i >= 0; i--) { if (suffix.lcp(i, i + 1) <= len) break; if (suffix.sa[i] < suffix.sa[p]) { p = i; } } memcpy(ans, s + suffix.sa[p], len); ans[len] = 0; return suffix.sa[p]; } int work2(char* ans) { int p = len * 2; while (suffix.sa[p] >= len) p--; for (int i = p - 1; i >= 0; i--) { if (suffix.lcp(i, i + 1) <= len) break; if (suffix.sa[i] > suffix.sa[p]) { p = i; } } memcpy(ans, s + suffix.sa[p], len); ans[len] = 0; return suffix.sa[p]; } int main() { int t; scanf("%d", &t); while (t--) { scanf("%d", &len); scanf("%s", s); for (int i = 0; i < len; i++) { s[len + i] = s[i]; } suffix = SuffixArray(s, len * 2); pos1 = work1(ans1); reverse(s, s + len * 2); suffix = SuffixArray(s, len * 2); pos2 = work2(ans2); pos2 = len - 1 - pos2; if (strcmp(ans1, ans2) > 0) { printf("%d 0\n", pos1 + 1); continue; }else if (strcmp(ans1, ans2) < 0) { printf("%d 1\n", pos2 + 1); continue; }else if (pos1 <= pos2) { printf("%d 0\n", pos1 + 1); continue; }else { printf("%d 1\n", pos2 + 1); } } return 0; }