题意:
给出n个数,然后定义一个v(l,r)为下标区间[l,r]中相同的点对数,如对于1,1,1这个序列来说,v(1,2)=1,v(1,3) =3,v(1,1)=0。
要求所有n*(n+1)/2个区间的价值第k小的是多少。
思路:
这题没做出来确实不应该。想到了尺取法,结果没想出来二分验证来找第k小的。
二分答案x=(l+r)/2,每次判断价值能大于x的区间有多少个,如果这个数目大于n*(n+1)/2-k,那么说明二分的结果小了,要在[x+1,r]范围内二分,否则要在[l,x]范围内继续二分,直到l=r。
关于怎么判断有多少个区间的价值大于x,采用尺取法即可。每次定一个左端点,然后看保证价值小于等于x的最远的右端点r是多少,那么res+=r-x+1。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 222222;
ll n, k;
int a[MAXN], cnt[MAXN];
bool C(ll x) {
ll now = 0, res = 0;
int st = 1, ed = 1;
memset(cnt, 0, sizeof(cnt));
while (st <= n) {
while (ed <= n && now + cnt[a[ed]] <= x)
now += cnt[a[ed++]]++;
res += n - ed + 1;
now -= --cnt[a[st++]];
}
//printf("%d\n", res);
if (res > n * (n + 1) / 2 - k) return false;
return true;
}
ll bin(ll l, ll r) {
while (l < r) {
//cout << l << " " << r << endl;
ll m = (l + r) >> 1LL;
if (C(m)) r = m;
else l = m + 1;
}
return r;
}
void discre() {
vector <int> vec;
for (int i = 1; i <= n; i++) vec.push_back(a[i]);
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int i = 1; i <= n; i++)
a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin() + 1;
}
int main() {
//freopen("in.txt", "r", stdin);
int T;
scanf("%d", &T);
while (T--) {
scanf("%lld%lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
discre();
printf("%lld\n", bin(0, n * (n + 1) / 2LL));
}
return 0;
}