51Nod 1686 第K大区间(二分,尺取,离散化)
题目链接
首先提一下第一句很重要,定义一个区间的值为其众数出现的#次数#,区间的值是次数而不是众数的值。这是一道经典的二分判定答案的题,如果从正面想的话不太好做,确实感觉无从下手,但是可以考虑一下判定答案,毕竟,判定比求解要简单。
因为\(n \leq 1e5\)所以,答案的范围就是\(1\)至\(n\)之间的一个数,如果用二分的话花费的时间是很可观的。但是这样又有了一个新的问题:如何判定每个解是否正确?如果把所有区间都列出来的话,时间复杂度至少也得有\(n^2\),还是会TLE。对于一个区间来说,如果区间第\(k\)大的数是我们要判定的一个解的话,如果把这个区间向左向右扩展,那么得到的区间第\(k\)大一定大于等于我们判定的解,这时,如果枚举每一个区间的最左端点,肯定存在一个右端点,以这个端点左端点做起点,终点大于等于右端点的区间的第\(k\)大肯定都会大于判定的解。所以,我们通过枚举左端点,找到各个右端点满足条件的最小值,就能在\(O(n)\)的时间之内计算出所有区间的值大于等于要判定的解的区间数,只要这个区间数等于或者大于(大于是一种特殊情况,比如所有区间众数的次数都一样就是大于),那么就可能有解。
但是还有一个问题,就是计算区间的众数。这里很容易想到开一个数组来计算每个数出现的次数,然后和判定的解比较。但是题目中的数字较大,直接开数组来计算是不现实的。所以可以考虑用\(map\)容器来计算(但是可能会卡常),也可以用离散化(因为我们只关心每个数之间的大小关系,并不关心它的值),时间复杂度会小一点。综上所述,所有的操作时间复杂度加起来一共是\(O(nlogn)\) 。
const int maxn = 1e5+10;
int n, k, arr[maxn], tmp[maxn], cnt[maxn];
bool checker(int num) {
ll sum = 0;
int l = 0;
zero(cnt);
for (int i = 0; i<n; ++i) {
++cnt[arr[i]];
while(cnt[arr[i]]>=num) {
sum += n-i;
--cnt[arr[l]];
++l;
}
}
return sum >= k;
}
int main(void) {
scanf("%d%d", &n, &k);
for (int i = 0; i<n; ++i) {
scanf("%d", &arr[i]);
tmp[i] = arr[i];
}
sort(tmp, tmp+n);
int p = unique(tmp, tmp+n)-tmp;
for (int i = 0; i<n; ++i)
arr[i] = lower_bound(tmp, tmp+p, arr[i])-tmp;
int l = 1, r = n;
while(l<=r) {
int mid = (l+r)>>1;
if (checker(mid)) l = mid+1;
else r = mid-1;
}
printf("%d\n", r);
return 0;
}