Description
众所周知,排序方法有许多种。例如:简单易懂的冒泡排序,平均复杂度较优的快速排序,以及不基于比较的基数排序等等。
现在,小 DD 得到了一个自然数序列 {a1,a2,⋯,an}{a1,a2,⋯,an}。他想要对其按照从小到大的顺序进行排序(即使得每个元素均严格不大于他的后继元素)。但由于基于比较的排序算法复杂度下界已经被证明为 Θ(nlog2n)Θ(nlog2n),所以小 DD 决定尝试一种全新的排序算法:翻转排序。
在翻转排序中,小 DD 在每次操作中,可以选择一个区间 [l,r][l,r] (1≤l≤r≤n)(1≤l≤r≤n),并翻转 al,al+1,⋯,aral,al+1,⋯,ar。即,在该次操作完后,序列将会变为 a1,a2,⋯,al−1,ar,ar−1,⋯,al+1,al,ar+1,ar+2,⋯,ana1,a2,⋯,al−1,ar,ar−1,⋯,al+1,al,ar+1,ar+2,⋯,an。
例如,对于序列 [1,6,2,4,3,5][1,6,2,4,3,5],若选择区间 [2,4][2,4] 进行翻转,则将会得到 [1,4,2,6,3,5][1,4,2,6,3,5]。
定义一次操作的代价为 r−l+1r−l+1,即翻转的区间长度。定义一个操作序列的代价为每次操作的代价之和。现在,请你帮助小 DD 求出一个代价足够小的操作序列(你并不一定要求出代价最小的操作序列)。
Input
第一行一个正整数 nn,表示序列长度。
第二行 nn 个空格隔开的非负整数,表示小 DD 得到的自然数序列 a1,a2,⋯,ana1,a2,⋯,an。
Output
输出若干行,每行两个空格隔开的正整数 l,rl,r (1≤l≤r≤n)(1≤l≤r≤n),表示一次翻转区间 [l,r][l,r] 的操作。
最后输出一行 -1 -1
,标志着操作序列的结束。
Sample Input
4 1 3 2 4
Sample Output
2 3 -1 -1
Hint
1.下发文件中提供了 checker.cpp
,该程序将对于其所在目录下的 sort.in
,判断其所在目录下的 sort.out
是否为一个正确的操作序列。若正确,将给出该操作序列的代价。若不正确,将给出错误信息。选手可以借助该程序来更好地检查自己的程序是否正确。
运行时,必须保证 sort.in
为一个合法的输入,且需保证 sort.out
符合题目中描述的输出格式,否则出现任何结果均有可能。
2.对于所有测试数据,保证 1≤n≤500001≤n≤50000,且 0≤ai≤1090≤ai≤109。
(附checker.cpp)
1 #include <algorithm> 2 #include <cstdio> 3 int arr[50005]; 4 int main() 5 { 6 FILE *fin = fopen("sort.in", "r"), *fout = fopen("sort.out", "r"); 7 if (!fin) 8 { 9 puts("INVALID : File sort.in not found."); 10 return -1; 11 } 12 if (!fout) 13 { 14 puts("INVALID : File sort.out not found."); 15 return -1; 16 } 17 int n; 18 fscanf(fin, "%d", &n); 19 for (int i = 0; i < n; i++) 20 fscanf(fin, "%d", arr + i); 21 int l, r, sum = 0; 22 while (~fscanf(fout, "%d%d", &l, &r)) 23 { 24 if (l == -1 && r == -1) 25 break; 26 sum += r - l + 1; 27 if (l <= 0 || l > n) 28 { 29 printf("INVALID : l = %d is not in range [1, %d]. ", l, n); 30 return -1; 31 } 32 if (r <= 0 || r > n) 33 { 34 printf("INVALID : r = %d is not in range [1, %d]. ", r, n); 35 return -1; 36 } 37 if (l > r) 38 { 39 printf("INVALID : %d = l > r = %d. ", l, r); 40 return -1; 41 } 42 if (sum > 20000000) 43 { 44 puts("INVALID : Too much cost."); 45 return -1; 46 } 47 std::reverse(arr + --l, arr + r); 48 } 49 bool f = true; 50 for (int i = 1; i < n; i++) 51 f &= arr[i] >= arr[i - 1]; 52 if (!f) 53 { 54 puts("INVALID : Not sorted."); 55 return -1; 56 } 57 printf("VALID : Total cost is %d. ", sum); 58 return 0; 59 }
Solution
本题算是一个比较新颖的题目,实际上是用这种翻转来模拟实现归并排序:
先将给定数列进行离散化,每次选定一个中间的数,将小于等于它的排在左边,大于它的排在右边,再依次递归两边就可以了;
主要是复杂度(进行操作的次数)证明a.... 排一遍要n,由于其非01性,所以进行log2n次的n排,故排一遍将l到r以stdd为标准换为左边小于等于stdd,右边大于等于stdd的操作数为Θ(nlog2n) ;
T(n) = 2T(n/2) + Θ(nlog2n),可以解得 T(n) = Θ(nlog22n) ;
上代码。
1 #include<bits/stdc++.h> 2 #define maxn 50005 3 using namespace std; 4 int n,pos[maxn]; 5 struct node{ 6 int v,id; 7 friend bool operator<(node a,node b){ 8 return a.v<b.v; 9 } 10 }nd[maxn]; 11 int stdd; 12 void run(int l,int r){ 13 if(l==r) return; 14 int mid=l+r>>1; 15 run(l,mid);run(mid+1,r); 16 17 int L,R;//要翻转的区间 18 L=R=mid;//L和R的初值 19 int i=mid,j=mid+1; 20 while(i>=l&&pos[i]>stdd) L=i--; 21 while(j<=r&&pos[j]<=stdd) R=j++; 22 if(L!=R){ 23 printf("%d %d ",L,R); 24 reverse(pos+L,pos+R+1); 25 } 26 } 27 void msort(int l,int r){ 28 if(l==r) return; 29 30 int mid=stdd=(l+r)>>1; 31 run(l,r);//将l到r以stdd为标准换为左边小于等于stdd,右边大于等于stdd 32 33 msort(l,mid);msort(mid+1,r); 34 } 35 void init(){ 36 scanf("%d",&n); 37 for(int i=1;i<=n;i++){ 38 scanf("%d",&nd[i].v);nd[i].id=i; 39 } 40 sort(nd+1,nd+n+1); 41 for(int i=1;i<=n;i++) pos[nd[i].id]=i;//离散化位置确定 42 43 msort(1,n); 44 printf("-1 -1 "); 45 } 46 int main(){ 47 //freopen("sort.in","r",stdin); 48 //freopen("sort.out","w",stdout); 49 init(); 50 51 return 0; 52 }