凉宫春日的叹息 [思维+二分]

凉宫春日的叹息

挺好的思维题。

题目描述

给定一个数组,将其所有子区间的和从小到大排序,求第 \(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

数据范围与提示

对于\(15\%\)的数据,\(n\leqslant 1000\)
对于\(30\%\)的数据,\(n\leqslant 5000\)
对于\(50\%\)的数据,\(n,k\leqslant 10^5\)
对于\(70\%\)的数据,\(n\leqslant 10^5\)
对于\(100\%\)的数据,\(n\leqslant 10^6,1\leqslant a[i],k\leqslant 10^9\)

分析

思路挺难看出来的,但是看出来就很好写了。

首先我们看到 \(k\) 的范围是 \(k\leqslant 10^9\) ,那么直接枚举前 \(k\) 大肯定是不行的,数组都开不下。但是最后的目的还是要去找到第 \(k\) 小的那个数,那么我们可以换一种枚举的方式:枚举值。

枚举排名不现实,所以我们就可以枚举当前第 \(k\) 小的可能值,然后判断一下他的排名到底是比 \(k\) 大还是比 \(k\) 小,然后二分答案即可。

求第几大的时候我们只需要枚举右端点,然后双指针搞一下就行。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6+10;
#define ll long long
ll a[maxn];
int n,k;
ll jud(ll mid){//判断当前值排名第几
	ll ans = 0;
	int j = 0;
	for(int i=1;i<=n;++i){
		while(a[i] - a[j] > mid)j++;//枚举右端点,更新双指针
		ans += i - j;//记录排名
	}
	return ans;
}
int main(){
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
		a[i] += a[i-1];
	}
	ll l = 0,r = a[n];
	while(l < r){//二分答案
		ll mid = l + r >> 1;
		if(jud(mid) >= k)r = mid;//排名比k大就缩小值
		else l = mid + 1;//否则增加
	}
	printf("%lld\n",l);
	return 0;
}