Problem 2 | 切序列(cut.cpp/c/pas) | ||
题目描述 | 何老板发现了一个有趣的数列问题,他想用它来考考你。 给你一个由n个非负整数构成的数列{A1, A2, ..., An} 其中有 A1 > A2, ..., An, 也就是第一项是数列中最大的一个数。 你要做的是把这个数列切割成3段,切割后把每一段的数字都前后颠倒,然后再把这三段都连接起来,得到一个新的数列,要求这个新的数列的字典序最小。详情见样例。 | ||
输入格式 | 第一行,一个正整数N 第二行,N个空格间隔的整数,表示给出的数列。 | ||
输出格式 | 一行,N个空格间隔的整数,表示所求的数列。 | ||
输入样例 | 输入样例1 | 输入样例2 | |
5 10 1 2 3 4 | 7 10 0 2 1 5 2 3 | ||
输出样例 | 输出样例1 | 输出样例2 | |
1 10 2 4 3 | 0 10 1 2 3 2 5 | ||
数据范围 | 对于50%的数据: 1≤N≤100 对于100%的数据: 1≤N≤200000 数列中的数字≤10000 | ||
样例说明 | 样例1说明: {10,1,2,3,4} -> {10,1|2|3,4} -> {1,10|2|4,3}-> {1,10,2,4,3} 样例2说明: {10,0,2,1,5,2,3}->{10,0|2,1|5,2,3}->{0,10|1,2|3,2,5}->{0,10,1,2,3,2,5} |
1.将输入的数组a反序存储到数组s
2.在数组s上求最小表示,得到结果x,则切割出的第一段为a数组中[x,n]这段区间 ;
3.在数组s的[1,x-1]区间求最小表示,得到结果y,则切割出的第二段为a数组的[y,x-1]这段区间;
4.显然,第三段为区间[1,y-1]
注意,为保证一定能切割出三段,所以第一次求最小表示的起点为3,第二次求最小表示的起点为2。
比如下列特例:
5
5 4 3 2 1
按常规来求的话,第一次最小表示求出的值为1,切割出的区间为[1,5],这样就无法在再切割其余两段了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
int a[200005],s[400005],n;
int work(int Len,int st){
int k,i=st,j=st+1;
while(i<=Len&&j<=Len){
for(k=0;k<Len;k++)
if(s[i+k]!=s[j+k])break;
if(k==Len)break;
if(s[i+k]>s[j+k])i+=k+1;
else if(s[i+k]<s[j+k])j+=k+1;
if(i==j)j++;
}
return min(i,j);
}//最小表示法
int main(){
int n,i,x,y;
scanf("%d",&n);
for(i=n;i>=1;i--)scanf("%d",&a[i]);
for(i=1;i<=n;i++){
s[i]=a[i];
s[i+n]=a[i];
}
x=work(n,3);
for(i=x;i<=n;i++)printf("%d\n",a[i]);
for(i=1;i<x;i++){
s[i]=a[i];
s[i+x-1]=a[i];
}
y=work(x-1,2);
for(i=y;i<x;i++)printf("%d\n",a[i]);
for(i=1;i<y;i++)printf("%d\n",a[i]);
}