2017ccpc哈尔滨 hdu 6231 B k-th number 题解 二分答案+尺取法

来源:互联网 发布:知乎 量化分析师 招聘 编辑:程序博客网 时间:2024/06/06 15:51

题目链接:https://cn.vjudge.net/problem/HDU-6231

Alice are given an array A[1..N]A[1..N] with NN numbers.

Now Alice want to build an array BB by a parameter KK as following rules:

Initially, the array B is empty. Consider each interval in array A. If the length of this interval is less than KK, then ignore this interval. Otherwise, find the KK-th largest number in this interval and add this number into array BB.

In fact Alice doesn’t care each element in the array B. She only wants to know the MM-th largest element in the array BB. Please help her to find this number.
Input
The first line is the number of test cases.

For each test case, the first line contains three positive numbers N(1≤N≤105),K(1≤K≤N),MN(1≤N≤105),K(1≤K≤N),M. The second line contains NN numbers Ai(1≤Ai≤109)Ai(1≤Ai≤109).

It’s guaranteed that M is not greater than the length of the array B.
Output
For each test case, output a single line containing the MM-th largest element in the array BB.
Sample Input
2
5 3 2
2 3 1 5 4
3 3 1
5 8 2
Sample Output
3
2
题意,t组数据,n,k,m,对于数列中每个连续的且长度大于k的区间,找出他的第k大数,然后在这些数中找出第m大的数。
做法:二分答案,然后难点变为怎么统计答案作为第K大的区间个数(这里说法是不严谨的)。首先做一个转化,对于每一个答案,要使该答案满足第m大,那么说把该答案带入到原数列中,肯定会有m个区间满足该答案是第k大,如果多了(区间数大于m)说明该答案小了,否则说明该答案大了。以此作为二分依据,那么该如何统计满足条件的区间数呢?利用尺取法即可。定义两个指针分别用来统计前面和后面的区间数。具体情况先看代码,然后我再用一组样例来解释。
code:

#include<iostream>#include<bits/stdc++.h>using namespace std;typedef long long int ll;int N,K;ll M;int a[100005];bool judge(int x){    ll ans=0;//区间个数    int num=0;//当前>x的数的个数    int j=1;    for(int i=1;i<=N;i++){        if(a[i]>=x)            num++;        //只要之前有k个数大于等于自身,自身就可以作为本区间的第k大数        if(num==K){            ans+=N-i+1;//统计后面一共可以形成多少个区间            while(a[j]<x){                ans+=N-i+1;//统计前面一共可以形成多少个区间                j++;            }            num--;//还原状态            j++;        }    }  // cout<<ans<<endl;    if(ans>=M)        return true;    else        return false;}int main(){    int t;    cin>>t;    while(t--){        scanf("%d%d%lld",&N,&K,&M);        for(int i=1;i<=N;i++)            scanf("%d",&a[i]);      //  int l=1,r=1000000000;     int l  = *min_element(a+1,a+N+1);     int r = *max_element(a+1,a+N+1);        int m;        //cout<<judge(1)<<" "<<judge(2)<<" "<<judge(3)<<endl;        while(l<r){            m=r-(r-l)/2;           // cout<<"m"<<"=  "<<m<<" ";            if(judge(m))                l=m;            else                r=m-1;        }        printf("%d\n",l);    }    return 0;}

下面图片多打印出来的部分是本代码跑一遍,二分过程中的每个答案m
和 每个答案m跑出来的 满足条件(即该答案代入数列,是第k大的数)的区间数是多少
这里写图片描述

看这组数据                            6 3 5           2 3 1 5 4 1满足a[i]>=k的数的个数分别是  1 2 2 3 4 4该组数据共有10个区间区间             第3大数2 3 1              13 1 5              1    1 5 4              15 4 1              12 3 1 5            23 1 5 4            31 5 4 1            12 3 1 5 4          33 1 5 4 1          32 3 1 5 4 1        3

此过程中二分到3,找出4个满足条件的区间
二分到2 找出5个满足条件的区间
这5个区间是怎么找出来的呢
首先扫描过程中扫到5时,开始有了三个数,此时ans+=(6-4+1),即ans+=3;
这时相当于统计上了
2 3 1 5
2 3 1 5 4
2 3 1 5 4 1
这三个区间
注意后两个区间本来第k大得数是3,但是这时候也统计上了,所以说其实这里满足条件的区间,到底满足什么条件呢?其实是不太好描述的,说是答案作为第K大的区间个数,其实不太对,应该是 答案及比答案大的数 做为区间第k大的 区间个数
计算式N-i+1,是统计5这个数连同它自己之后的区间,而前指针j这里作为统计之前的区间由于并不满足a[j]

int main(){    int t;    cin>>t;    while(t--){        scanf("%d%d%lld",&N,&K,&M);        for(int i=1;i<=N;i++)            scanf("%d",&a[i]);       int l=1,r=1000000000;        int m;        while(l<r){            m=(l+r)/2;           // cout<<"m"<<"=  "<<m<<" ";            if(judge(m))                l=m+1;            else                r=m;        }        printf("%d\n",l-1);    }    return 0;}

另一种写法:

int main(){    int t;    cin>>t;    while(t--){        scanf("%d%d%lld",&N,&K,&M);        for(int i=1;i<=N;i++)            scanf("%d",&a[i]);      //  int l=1,r=1000000000;     int l  = *min_element(a+1,a+N+1);     int r = *max_element(a+1,a+N+1);        int m;        //cout<<judge(1)<<" "<<judge(2)<<" "<<judge(3)<<endl;        while(l<r){            m=r-(r-l)/2;           // cout<<"m"<<"=  "<<m<<" ";            if(judge(m))                l=m;            else                r=m-1;        }        printf("%d\n",l);    }    return 0;}

这两种写法一种是适用于整数1到10000000这样式的二分,所得答案为l-1,一种是更普遍的,对左右端点没有要求,所的答案是l,但是对于取整方法却有了更高的要求,即mid = r-(r-l)/2;

原创粉丝点击