Time Limit: 5000 MS Memory Limit: 0 KB 64-bit integer IO format: %I64d , %I64u Java class name: Main
Description Given a sequence, {A1, A2, …, An} which is guaranteed A1 > A2, …, An, you are to cut it into three sub-sequences and reverse them separately to form a new one which is the smallest possible sequence in alphabet order. The alphabet order is defined as follows: for two sequence {A1, A2, …, An} and {B1, B2, …, Bn}, we say {A1, A2, …, An} is smaller than {B1, B2, …, Bn} if and only if there exists such i ( 1 ≤ i ≤ n) so that we have Ai < Bi and Aj = Bj for each j < i.
Input The first line contains n. (n ≤ 200000) The following n lines contain the sequence.
Output output n lines which is the smallest possible sequence obtained.
Sample Input 5 10 1 2 3 4
Sample Output 1 10 2 4 3
Hint {10, 1, 2, 3, 4} -> {10, 1 | 2 | 3, 4} -> {1, 10, 2, 4, 3}
Source POJ Founder Monthly Contest – 2008.04.13, Yao Jinyu
题意: 将序列分成3段,每一段都不为空,然后分别翻转之后得到一个字典序最小的序列,输入加while就WA了
第一段: 该题要分成3段且每一段都不为空,第一段反过来的字典序必定要最小这样才能使开头最小,我们可以先将整个数组翻转过来,然后除开最后2个(最后至少要剩下两个组成最后两段,每段1个)之外利用后缀数组寻找字典序最小的然后输出即可。由于有前置条件A1 > A2, …, An的限制,第一段的分离可以单独考虑,也就是说不会出现如下不适用这样分段解法的情况: 样例(我们只讨论第一段,这个反例建议理解思路和代码后推导): 6 1 1 2 1 3 3 假如我们第一段利用后缀数组找最小的字典序,除开最后两个3 3,剩下1 1 2 1中字典序最小的是1,然后在剩余的1 2 1 3 3中得到最小的便是1 2 1 3 3,组合起来就输1 1 2 1 3 3很明显这个分段可以分成1 1 1 2 3 3({1 1 | 2 1 | 3 3})。
剩余两段: 这两段不能单独考虑了,就想上述“不适用”例子一样,这里没保证剩余的段有什么前提条件,因此必须两端同时考虑。借助《挑战程序设计学竞赛》一书的思想,假设我们剩余两段最终分解为1|2,那么剩余两段的在原序列的顺序为1->|2->,翻转之后为<-1|<-2。 此处解决的方法为:在分离第一段之前时候我们先翻转整个序列(原序列:第一段|1->|2->),然后利用后缀数组找到第一段并输出,前面剩余的序列便为<-2|<-1(这里第一段已输出然后可以不考虑了)。我们将其复制一遍得到<-2|<-1|<-2|<-1,现在我们可以在这个数组中利用后缀数组寻找长度为除开第一段之后剩余长度的最小字典序的序列(好拗口)。输出的时候一定要找到的序列开始范围在[1, n)之间的,这样能保证两段都不为空。因为我们假设最终分解为1|2,所以直接输出的就是<-1|<-2,也就是1->|2->的翻转。
Accepted:
#include <cstdio> #include <algorithm> using namespace std; const int Maxn = 200005; int n, k; int Rank[2*Maxn], tmp[2*Maxn], sa[2*Maxn], a[2*Maxn]; void reverseArray(int l, int r) { while(l<r) { int temp = a[l]; a[l++] = a[r]; a[r--] = temp; } } bool compareSa(const int i, const int j) { if(Rank[i]!=Rank[j]) return Rank[i]<Rank[j]; int ri = (i+k>n)?-1:Rank[i+k]; int rj = (j+k>n)?-1:Rank[j+k]; return ri<rj; } void getSa() { for(int i = 0; i < n; ++i) { sa[i] = i; Rank[i] = a[i]; } for(k = 1; k <= n; k*=2) { sort(sa, sa+n, compareSa); tmp[sa[0]] = 0; for(int i = 1; i < n; ++i) tmp[sa[i]] = tmp[sa[i-1]] + (compareSa(sa[i-1], sa[i])?1:0); for(int i = 0; i < n; ++i) Rank[i] = tmp[i]; } } int main() { scanf("%d",&n); // 输入加while就WA了 for(int i = 0; i < n; ++i) scanf("%d", &a[i]); reverseArray(0, n-1); getSa(); for(int i = 0; i <= n; ++i) if(sa[i]>1) { for(int j = sa[i]; j < n; ++j) printf("%d\n", a[j]); n = sa[i]; break; } for(int i = 0; i < n; ++i) //Copy a[i+n] = a[i]; n<<=1; getSa(); for(int i = 0; i <= n; ++i) if(sa[i]>0 && sa[i]<n/2) { int first = sa[i], last = sa[i]+n/2; while(first<last) printf("%d\n", a[first++]); break; } return 0; }这里总结一下我的错误: WA/RE/TLE等,我只贴出一个代码供参考:
#include <cstdio> #include <algorithm> using namespace std; const int Maxn = 200005; int n, k; int Rank[2*Maxn], tmp[2*Maxn], sa[2*Maxn]; void reverseArray(int *a, int l, int r) { while(l<r) { int temp = a[l]; a[l++] = a[r]; a[r--] = temp; } } bool compareSa(const int i, const int j) { if(Rank[i]!=Rank[j]) return Rank[i]<Rank[j]; int ri = (i+k>n)?-1:Rank[i+k]; int rj = (j+k>n)?-1:Rank[j+k]; return ri<rj; } void getSa(int *a) //这个函数的排序可以省掉空字符串,也就是n可以不用考虑,对照AC代码 { for(int i = 0; i <= n; ++i) { sa[i] = i; Rank[i] = (i==n)?-1:a[i]; } for(k = 1; k <= n; k*=2) //此处一定要遍历到k<=n 不能是k<=n/2,可以拿n是奇数的时候来模拟一遍 { sort(sa, sa+n+1, compareSa); tmp[sa[0]] = 0; for(int i = 1; i <= n; ++i) tmp[sa[i]] = tmp[sa[i-1]] + (compareSa(sa[i-1], sa[i])?1:0); for(int i = 0; i <= n; ++i) Rank[i] = tmp[i]; } } void stringCopy(int* a, int* b, const int num) { for(int i = 0; i < num; ++i) a[i] = b[i]; } int a[Maxn]; //此处数组不够大,复制的时候有可能是2*Maxn int main() { scanf("%d",&n); for(int i = 0; i < n; ++i) scanf("%d", &a[i]); reverseArray(a, 0, n-1); getSa(a); // for(int i = 1; i <= n; ++i) // if(sa[i]>1) // 我个人认为在把“空字符串”考虑进最小字典中的时候 // 实际上肯定有sa[0]==n(反正我没举出反例),那么从1开始就行了,但是提交的时候就是TLE // 改成 // for(int i = 0; i <= n; ++i) // if(sa[i]>1 && sa[i]<n) // 就过了 for(int i = 1; i <= n; ++i) if(sa[i]>1) { for(int j = sa[i]; j < n; ++j) printf("%d\n", a[j]); n = sa[i]; break; } stringCopy(a+n, a, n); n<<=1; getSa(a); for(int i = 1; i <= n; ++i) if(sa[i]>0 && sa[i]<n/2) { for(int j = 0; j < n/2; ++j) printf("%d\n", a[sa[i]+j]); break; } return 0; }