凉宫春日的叹息「二分求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;
}