涼宮春日的嘆息
挺好的思維題。
題目描述
給定一個陣列,將其所有子區間的和從小到大排序,求第 \(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;
}