题目:
一个长为n的序列a[],所有长度≥k的区间取出第k大的数形成一个新序列b[]。问b[m]的值。
做法:
显然我们不可能求出b中所有数然后取出第m个。
所以想到二分这个数,设为x。然后问题就转化成判定性问题:a[]中所有连续区间的第k大的数≥x的区间个数是否≥m。用滑动窗口的做法使这个judge复杂度为O(n),可以解决问题。
具体做法就是枚举每个位置作为区间的左端点l,找到对应的右端点r,使[l,r]区间中恰有k个≥x的数。这样我们就能一次统计出左端点为l的且第k大数≥x的区间个数:n-r+1。(r往后每一个位置都满足)。然后左端点l+1时右端点只需在当前的基础上向右扩展就行了。整个过程类似一个窗口在滑动,所以被称为滑动窗口算法,(也被称为双指针或尺取法)。
代码:
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define debug(a) cout << #a ": " << a << endl
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
int n, k, a[N];
ll m;
bool judge(int x){
int r = 0, cnt = 0;
ll ans = 0;
for (int l = 1; l <= n; ++l){
while (cnt < k && r < n){
if (a[++r] >= x) cnt++;
}
if (cnt == k) ans += n-r+1;
if (a[l] >= x) cnt--;
}
return ans >= m;
}
int main(void){
IOS;
int T; cin >> T;
while (T--){
cin >> n >> k >> m;
int mx = 0;
for (int i = 1; i <= n; ++i) cin >> a[i], mx = max(mx, a[i]);
int l = 1, r = mx, ans;
while (l <= r){
int mid = (l+r) >> 1;
if (judge(mid)){
ans = mid;
l = mid+1;
}else{
r = mid-1;
}
}
cout << ans << endl;
}
return 0;
}
京公网安备 11010502036488号