hihocoder - 1483 二分 + 尺取法

题意:

给出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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值