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;   
}
posted @ 2020-04-17 11:03  shuitiangong  阅读(142)  评论(0编辑  收藏  举报