HDU 6040 2017多校第一场

题意说的就是通过那个函数来产生随机数,读入的 ABC 其实就是一个随机数的种子,用来产生n个数用,然后是读入m个询问,对于每个询问在n个数中求取第bi小的数。因为n可以去到 1e7 ,想要直接通过 nlogn 的排序来做是不可能的然后这题的询问数量比较少,如果能在 o(n) 的时间内得到元素的第 k 小就是这题的突破口。


官方题解
最慢的情况是 b 的取值为 0, 1, 2, 3, 5, 8, 的情况,但事实上也只有 O(log1.618n)O(log1.618n) 个取值。

从最大的取值到最小的取值依次使用近似线性复杂度的求第 k 小的方法即可,该方法的思想与快排相似,可以保证前 k - 1小的元素都放置在第 k 小元素的前面,这样枚举的时候就可以依次减少每次的枚举量,时间复杂度 Oi0n1.618i=O(n)O(i01.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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值