题目大意
小 n o d nod nod 新学了快速排序,并且学会了用 r a n d o m random random 函数获取随机中枢避免最坏复杂度的出现。 代码如下:
void Qsort(int a[], int low, int high) {
if(low >= high) return;
int first = low;
int last = high;
int key_index = (rand() % (high - low + 1)) + low;
swap(a[first], a[key_index]);
int key = a[first]; /*用数组的第一个记录作为枢轴*/
while(first < last) {
while(first < last && a[last] >= key) --last;
a[first] = a[last]; /*将比第一个小的移到低端*/
while(first < last && a[first] <= key) ++first;
a[last] = a[first]; /*将比第一个大的移到高端*/
}
a[first] = key; /*枢轴记录到位*/
Qsort(a, low, first - 1);
Qsort(a, first + 1, high);
}
号称这份代码跑的比谁都快,并且到处找人炫耀。
夹克老爷 为了教育他,事先更改了测试机的环境,构造了大小为 k k k 的数组 r r r ,使得快排开始后,第 i i i 次调用 r a n d rand rand 函数时返回的是 r [ i r[i%k] r[i (注意,这里的 i i i 从第 0 0 0 次开始),给出 r r r 数组中的 k k k 个数。
你能构造一个 1 − N 1-N 1−N 的排列让测试机上运行的快排代码递归深度最大吗? ( Q s o r t (Qsort (Qsort 函数的递归调用深度最大,不考虑尾递归优化)。
** P S PS PS :** 上面这句话里的“第 i i i 次调用”是从第 0 0 0 次调用开始的… 也就是简单的按照从前往后循环使用 r r r 数组… 输出递归深度最大的排列中字典序最小的那个排列。
例如: 3 1 3\ 1 3 1 0 0 0 每次选择第 0 0 0 个数字作为枢轴,最大递归 3 3 3 层, ( 1 , 2 , 3 ) (1,2,3) (1,2,3) 、 ( 3 , 2 , 1 ) (3,2,1) (3,2,1) 、 ( 3 , 1 , 2 ) (3,1,2) (3,1,2) 等排列都符合要求,其中字典序最小的是 ( 1 , 2 , 3 ) (1,2,3) (1,2,3) 答案为 ( 1 , 2 , 3 ) (1,2,3) (1,2,3)
3 1 3\ 1 3 1 1 1 1 每次选择第 1 1 1 个数字作为枢轴,最大递归 3 3 3 层, ( 2 , 1 , 3 ) (2,1,3) (2,1,3) 、 ( 3 , 1 , 2 ) (3,1,2) (3,1,2) 、 ( 1 , 3 , 2 ) (1,3,2) (1,3,2) 等排列都符合要求,其中字典序最小的是 ( 1 , 3 , 2 ) (1,3,2) (1,3,2) 答案为 ( 1 , 3 , 2 ) (1,3,2) (1,3,2)
4 2 4\ 2 4 2 1 2 1\ 2 1 2 循环使用 1 1 1 、 2 2 2 数字作为枢轴,最大递归 4 4 4 层, ( 3 , 4 , 1 , 2 ) (3,4,1,2) (3,4,1,2) 、 ( 3 , 1 , 4 , 2 ) (3,1,4,2) (3,1,4,2) 、 ( 1 , 4 , 3 , 2 ) (1,4,3,2) (1,4,3,2) 等排列都符合要求,其中字典序最小的是 ( 1 , 4 , 3 , 2 ) (1,4,3,2) (1,4,3,2) 答案为 ( 1 , 4 , 3 , 2 ) (1,4,3,2) (1,4,3,2)
输入格式
第一行两个整数 N , K . ( 1 ≤ N ≤ 50000 , 1 ≤ K ≤ 50000 ) N, K.(1 \le N \le 50000, 1 \le K \le 50000) N,K.(1≤N≤50000,1≤K≤50000) 。 第 2 ∼ K + 1 2 \sim K + 1 2∼K+1 行:每行 1 1 1 个数对应数组 r r r 中的元素。 ( 0 ≤ r [ i ] ≤ 1 0 9 ) (0 \le r[i] \le 10^9) (0≤r[i]≤109)
输出格式
N N N 行整数, 1 ∼ N 1\sim N 1∼N 的一个排列,使得上述快排代码递归深度最大,输出其中字典序最小的那个排列。
输入样例
3 1
0
输出样例
1
2
3
基本思路
其实我们观察题目和结合快排的工作原理不难发现,快排是每次选择一个数,以这个数为中心划分,比这个数小的划在左边,比这个数大划的右边。
那么如何让递归层数变最大呢,很简单:每次我们不是都会划出两部分进行递归吗?我们就让其每次只划出来一个数,其他数全在另一个分支,这样递归层数就变大了。要实现这个,我们只需要让每次选出来做枢轴进行比较的数最大或最小就行了。其实快排中选用随机枢轴就是这个道理,但是在这道题中随机枢轴有生成规律,我们就可以依据这个规律来卡它。
这题还有一个恶心的点,就是它要输出字典序最小的序列。我们首先明确字典序是如何比较的:从前往后看,只要碰到一个不一样的,比较它们的大小,后面的都不用管了,例如
1
3
2
1\ 3\ 2
1 3 2 和
2
1
3
2\ 1\ 3
2 1 3 开头第一个
1
1
1 比
2
2
2 小,后面
3
3
3 比
1
1
1 大就不用管了。我们来考虑一下,对于一个点它是填最大还是最小,我们假设此时最大为
n
n
n,最小为
1
1
1。分两种情况:
- 此时选择的枢轴不是第一个数:因为前面还有数,要把小的数留给前面,所以要填最大。假如我们填 1 1 1了,如: 2 3... n 1 2\ 3...n\ 1 2 3...n 1 我可以十分变态地把前面所有都减 1 1 1 得到 1 2... ( n − 1 ) n 1\ 2...(n-1) \ \ n 1 2...(n−1) n
- 此时选择的枢轴是第一个数:因为是第一个我们要毫不犹豫地填最小的。但是我们要注意,这里的第一个是指原序列中的靠前的位置,而非快排中正在划分排序的序列,快排中的序列顺序已经被破坏了。
然后就是模拟题目中给出的快排了,但是绝对不能复制题目中的快排,因为它自己都被卡到 n 2 n^2 n2的复杂度了,你用它来模拟绝对超时。我们可以来观察一下它的排序规律:首先把选择出来的枢轴与当前序列中第一个数交换位置,如果枢轴里填的是最小的话就不用管了,如果填的是最大的话最终要把它和当前序列中最后一个位置再交换一下。
核心代码
#include<bits/stdc++.h>
using namespace std;
const int N=50005;
int n,k,r[N],res[N],id[N];
int main(){
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=0;i<k;i++)
cin>>r[i];
int min_num=1,max_num=n,min_idx=0;
//这里使用min_num和max_num维护正在排序的区间
//最小的在min_num前面,最大的在max_num后面,中间的是正在排序的数
//同时这两个变量还代表可供使用的最小和最大数字
for(int i=0;i<n;i++){
id[i]=i;
res[i]=-1;
}
for(int i=0;i<n;i++){
int key_index=min_num-1+r[i%k]%(max_num-min_num+1);//选择枢轴
if(id[key_index]==min_idx){//在第一个位置
//min_idx表示在原序列中最小的位置
res[min_idx]=min_num;
swap(id[key_index],id[min_num-1]);
//交换指向的数
++min_num;
for(;min_idx<n&&res[min_idx]>=0;)
++min_idx;
}
else{
res[id[key_index]]=max_num;
swap(id[key_index],id[min_num-1]);
swap(id[max_num-1],id[min_num-1]);
--max_num;
}
}
for(int i=0;i<n;i++)
cout<<res[i]<<endl;
return 0;
}