题目:
一个长为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; }