【51nod 1686】第K大区间 二分、尺取

1686 第K大区间

定义一个区间的值为其众数出现的次数
现给出n个数,求将所有区间的值排序后,第K大的值为多少。

输入

第一行两个数n和k(1<=n<=100000,k<=n*(n-1)/2)
第二行n个数,0<=每个数<2^31

输出

一个数表示答案。

输入样例

4 2
1 2 3 2

输出样例

2

思路:

两个性质:1、序列的长度是 n ,所以序列可能的最大值也就是 n 。

                  2、对于一个序列的值如果是 num ,那么:必定存在序列的值是 num-1 、num-2、num-3 …… 1 。

 

设 fun( num ) 是 ” 值大于 num 的序列 “ 的数量,那么我们就有:

                                     当 fun( num ) == k ,  那么最终结果就是 num+1 ;

                                     当 fun( num ) > k 时,最终结果就应该大于 num ;

                                     当 fun( num ) < k 时,最终结果就应该小于或等于 num 。

所以可以对 num 在 1~n 范围内进行二分答案,每次可以用尺取可以在 O(n) 计算出 fun( num ).

#include<bits/stdc++.h>
using namespace std;
typedef long long Lint;
const int max_n = 1e5+1e2;

int a[max_n*2];
vector<int> v;
int n;
Lint k;
int flag[max_n*2];

/*
假设我们求出区间 <l,r> 的值大于 num ,那么区间 < L , R > (L=1~l, R=r~n) 的值也大于num
*/
Lint fun(int num){ //此函数求出是值大于num的区间
    Lint res=0;
    memset(flag,0,sizeof(flag));
    int l=1,r=1;
    flag[a[1]]=1;
    while(true){
        while(r<=n && flag[a[r]]<=num){
            r++;
            flag[a[r]]++;
        }
        if(r>n) break;
        Lint temp=l;
        while(flag[a[r]]>num&&l<=r){
            flag[a[l]]--;
            l++;
        }
        temp=(Lint)l-temp;
        res+=temp*(Lint)(n-r+1); //每次结果加上 r后面数字的个数*l移动的距离
    }
    return res;
}

void Init(){  //数字较大所以离散化一下
    scanf("%d %lld",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        v.push_back(a[i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
    }
}

void solve(){
    Init();
    int l=1,r=n,mid;
    while(r-l>1){
        mid=(l+r)/2;
        Lint temp=fun(mid);
        if(temp==k){
            printf("%d\n",mid+1);
            return ;
        }else if(temp>k){
            l=mid;
        }else if(temp<k){
            r=mid;
        }
    }
    if(fun(l)>=k){
        printf("%d\n",r);
    }else{
        printf("%d\n",l);
    }
}

int main(){
    solve();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值