凉宫春日的叹息「二分求K小」

凉宫春日的叹息「二分求K小」

题目描述

给定一个数组,将其所有子区间的和从小到大排序,求第 (k) 小的是多少。

输入格式

第一行两个数 (n), (k),表示数组的长度和 (k)

第二行有 (n) 个数,第 (i) 个是 (a[i]),表示给定的数组。

输出格式

仅一个数,表示答案。

样例

样例输入1

5 6
1 1 1 1 1

样例输出1

2

样例输入2

8 20
2 3 1 2 5 3 2 3

样例输出2

8

数据范围

对于 (100\%) 的数据, (n <= 10^6),(1<=a[i]),(k <= 10^9)

思路分析

  • 好像前段时间学长在有一次讲课的时候说了一下求 (k) 大/小 的方法,其中有一个就是二分
  • 所以这题我们考虑怎么用二分做,检验肯定就是枚举 (mid) 看能不能凑出k个小于等于 (mid) 的即可
  • 但是不同的区间是有很多的,如果枚举出所有再进行二分检验,显然时间效率是不允许的。但其实有一个很显然的性质,就是如果一个区间符合条件(即比 (mid) 要小),那么该区间内的所有区间都会满足条件。
  • 用前缀和,固定右端点变化坐端点就彳亍了,可以快速得出答案

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define int long long //奇巧淫技
#define N 1000010
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,k,a[N],sum[N];
int check(int mid) {
    int j = 0;
    int res = 0;
    for (int i = 1; i <= n; i++) {
        while (sum[i] - sum[j] > mid) j++; //不符合就一直加
        res += i - j; //跳出循环后,这个区间里所有右端点为i的都符合条件
    }
    return res;
}

signed main() {
    n = read(),k = read();
    for (int i = 1; i <= n; i++) {
        a[i] = read();
        sum[i] = sum[i - 1]+a[i];
    }
    int l = 0, r = sum[n], mid;
    while (l < r) {
        mid = (l + r) >> 1;
        if (check(mid) >= k) r = mid;
        else l = mid + 1;
    }
    printf("%lld
", r);
    return 0;
}
原文地址:https://www.cnblogs.com/hhhhalo/p/13504646.html