题意说的就是通过那个函数来产生随机数,读入的 A,B,C 其实就是一个随机数的种子,用来产生n个数用,然后是读入m个询问,对于每个询问在n个数中求取第bi小的数。因为n可以去到 1e7 ,想要直接通过 nlogn 的排序来做是不可能的然后这题的询问数量比较少,如果能在 o(n) 的时间内得到元素的第 k 小就是这题的突破口。
官方题解
最慢的情况是
从最大的取值到最小的取值依次使用近似线性复杂度的求第 k 小的方法即可,该方法的思想与快排相似,可以保证前 k - 1小的元素都放置在第 k 小元素的前面,这样枚举的时候就可以依次减少每次的枚举量,时间复杂度 O⎛⎝∑i≥0n1.618i⎞⎠=O(n)O(i≥0∑1.618in)=O(n) 。
回想一下求解序列中第k小的方法,说一个简单的方法就是二分第k小的值,然后扫一遍序列看这个数之前是不是真的有k-1数。时间复杂度也是
nlogn
。这种方法可以不破坏原序列。
然后可以借助快排的思想进行优化,随便在序列中拿一个数做枢轴,在扫的时候,顺便通过交换操作使得二分的这个值作为枢轴放对位置
情况如果枢轴前面刚好有k-1个数,那么这个数就是我们想要的。
如果从前往后包括枢轴只有a个不够k个,说明这个枢轴太小,这时候利用线段树的思想,只要在比枢轴大的那部分递归的找第k-a小的数。
如果从前往后包括枢轴有b个超过k个了,说明这个枢轴太大,这时候利用线段树的思想,只要在比枢轴小的那部分递归的找第k小的数。
这样处理的好的话是可以在
o(n)
时间里面得到第k小的数的,但是要破坏原序列作为代价。
c++语言里面真巧有上面所说的 o(n) 时间求第k小的那种方法,nth_element,这个函数正式我们想要的。除此之外,这题的询问还要做一下处理,先做大的询问再做小的询问,这样就能利用之前的信息加快询问的速度。
#include<bits/stdc++.h>
using namespace std;
int n,m;
unsigned x , y , z ;
unsigned rng61() {
unsigned t;
x ^= x << 16;
x ^= x >> 5;
x ^= x << 1;
t = x;
x = y;
y = z;
z = t ^ x ^ y;
return z;
}
int main()
{
if (fopen("in.txt", "r") != NULL)
{
freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
}
ios::sync_with_stdio(0);
int icase=0;
while(cin>>n>>m>>x>>y>>z)
{
cout<<"Case #"<<++icase<<": ";
vector<unsigned> a(n);
for(auto &ai : a)
ai=rng61();
vector<pair<int,int>> b(m);
for(int i=0;i<b.size();i++)
{
int bi;
cin>>bi;
b[i]={bi,i};
}
sort(b.begin(),b.end());
vector<unsigned> ans(m);
auto it = a.end();
for(int i=b.size()-1;i>=0;i--)
{
if(i!=b.size()-1&&b[i].first==b[i+1].first)
ans[b[i].second]=ans[b[i+1].second];
else
{
nth_element(a.begin(),a.begin()+b[i].first,it);
it=a.begin()+b[i].first;
ans[b[i].second]=*it;
}
}
for(int i=0;i<ans.size();i++)
{
if(i) cout<<' ';
cout<<ans[i];
}
cout<<'\n';
}
return 0;
}