涼宮春日的嘆息 [思維+二分]

Vocanda發表於2020-08-14

涼宮春日的嘆息

挺好的思維題。

題目描述

給定一個陣列,將其所有子區間的和從小到大排序,求第 \(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;
}

相關文章