【想法】2013-2014 ACM-ICPC, NEERC, Moscow Subregional Contest - K - Top K Elements

题目链接https://codeforces.com/gym/100257


题意

给出一个递推式 x i = ( A ⋅ x i − 2 + B ⋅ x i − 1 + C ) m o d 2 31 x_i = (A · x_{i−2} + B · x_{i−1} + C) mod 2^{31} xi=(Axi2+Bxi1+C)mod231,问你求出前 n n n项的前 k k k大。
1 ≤ n ≤ 1 e 8 , 1 ≤ k ≤ 2 e 5 1\leq n \leq 1e8, 1\leq k\leq 2e5 1n1e8,1k2e5


题解一

队友的神仙想法:把值域为 2 31 2^{31} 231的以 10000 10000 10000为一个块长,分成大概 2 e 5 2e5 2e5个块,扫一遍递推式,累加各个块内值的个数。然后从后往前做一遍后缀和,就能够知道第 k k k大是落在哪个块内。再对这个块内的做一遍后缀和,就可以知道确切的第 k k k大是多少。知道第 k k k大之后,就只要再扫一遍递推式,就可以输出答案了。
不同的写法可能会是三倍或四倍的 O ( n ) O(n) O(n),写的不好看了可能会TLE (比如说我)。其实这题模数非常特殊: 2 31 2^{31} 231,正好是int的大小,所以可以先用unsigned int保存让它自然溢出,最后与mod-1与一下就行了。这样子操作就会非常非常快,写丑了也没关系。


#include<bits/stdc++.h>
using namespace std;
typedef unsigned int ll;
const int N=3e5+7;
const ll mod=(((ll)1)<<31)-1;
ll ar[N];
ll n,k;
ll x,y,xx,yy,a,b,c;
ll tot;
int main(){
    scanf("%u%u",&n,&k);
    scanf("%u%u%u%u%u",&x,&y,&a,&b,&c);
    ll ma=0;
    xx=x;yy=y;
    for(ll i=1;i<=n;i++){
        ll now=(xx*a+yy*b+c)&mod;
        ar[now/10000]++;
        xx=yy;yy=now;
        ma=max(ma,now);
    }
    
    ll p,num;
    for(ll i=ma/10000;i>=0;i--){
        ar[i]=ar[i]+ar[i+1];
        if(ar[i]>=k){
            num=ar[i+1];
            p=i;
            break;
        }
    }
    
    for(ll i=0;i<N;i++) ar[i]=0;
    ll le=p*10000;
    ll ri=le+10000-1;
    xx=x;yy=y;
    for(ll i=1;i<=n;i++){
       ll now=(xx*a+yy*b+c)&mod;
        xx=yy;yy=now;
        if(now/10000==p){
            ar[now-le]++;
        }
    }
    ll lim=0;
    for(ll i=10000;i>=0;i--){
        ar[i]=ar[i]+ar[i+1];
        if(ar[i]>=k-num){
            lim=le+i;
            break;
        }
    }
    tot=0;
    xx=x;yy=y;
    for(ll i=1;i<=n;i++){
        ll now=(xx*a+yy*b+c)&mod;
        xx=yy;yy=now;
        if(now>lim) ar[++tot]=now;
    }
    sort(ar+1,ar+1+tot,greater<ll>());
    bool fst=true;
    for(ll i=1;i<=tot;i++){
        if(fst) fst=false;
        else printf(" ");
        printf("%d",ar[i]);
    }
    while(tot<k){
        if(fst) fst=false;
        else printf(" ");
        printf("%d",lim);
        tot++;
    }
}

题解二

还有一个神仙做法是用nth_element。这是STL的一个函数,能够 O ( n ) O(n) O(n)地求出第 k k k大。这样就只要维护一个 1 e 6 1e6 1e6左右的数组,每次算出的数如果大于第 k k k大就加入数组,一旦数组存不下了就重新计算出当前第 k k k大,并把数组大小设为 k k k也就是 2 e 5 2e5 2e5。这样差不多只会计算几百次第 k k k大,复杂度也是 O ( n ) O(n) O(n)级别。


#include<bits/stdc++.h>
using namespace std;
typedef unsigned int ll;
const int N=1e6+7;
const ll mod=(((ll)1)<<31)-1;
ll ar[N];
ll n,k,x,y,a,b,c,xx,yy,tot,kth;
int main(){
    scanf("%u%u",&n,&k);
    scanf("%u%u%u%u%u",&x,&y,&a,&b,&c);
    xx=x;yy=y;
    for(ll i=1;i<=n;i++){
        ll now=(xx*a+yy*b+c)&mod;
        if(now>kth){
            ar[++tot]=now;
            if(tot==N-1){
                nth_element(ar+1,ar+1+k-1,ar+1+tot,greater<ll>());
                kth=ar[k];
                tot=k;
            }
        }
        xx=yy;yy=now;
    }
    nth_element(ar+1,ar+1+k-1,ar+1+tot,greater<ll>());
    kth=ar[k];
    xx=x;yy=y;
    tot=0;
    for(ll i=1;i<=n;i++){
        ll now=(xx*a+yy*b+c)&mod;
        if(now>kth) ar[++tot]=now;
        xx=yy;yy=now;
    }
    sort(ar+1,ar+1+tot,greater<ll>());
    bool fst=true;
    for(ll i=1;i<=tot;i++){
        if(fst) fst=false;
        else printf(" ");
        printf("%u",ar[i]);
    }
    while(tot<k){
        if(fst) fst=false;
        else printf(" ");
        printf("%u",kth);
        tot++;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值