b_vj_K-th Number(二分+线段树)

有一个数列a[1~N]中 (数字各不相同),输入m行i,j,k,目的是求a[i...j]之间第K小的数

思路:二分+线段树:二分枚举数字num,然后在线段树的每个区间中找小于num的个数c,根据c待定二分边界

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
ll n,m,a[N];
vector<ll> t[N];
void pushup(int k) {
    merge(t[k<<1].begin(), t[k<<1].end(), t[k<<1|1].begin(), t[k<<1|1].end(), t[k].begin());
}
void build(int l, int r, int k) {
    if (l==r) {
        t[k].push_back(a[l]);
        return;
    }
    int m=l+r>>1;
    build(l,m,k<<1);
    build(m+1,r,k<<1|1);
    t[k].resize(r-l+1);
    pushup(k);
}
int ask(int ql, int qr, int l, int r, int k, ll num) { //求ql,qr中找有多少个数字小于num
    if (ql<=l && r<=qr) {
        return upper_bound(t[k].begin(), t[k].end(), num)-t[k].begin(); //大于num的第一个位置,能到num
    }
    int m=l+r>>1, ans=0;
    if (m>=ql) ans+=ask(ql,qr,l,m,k<<1,num);
    if (m<qr)  ans+=ask(ql,qr,m+1,r,k<<1|1,num);
    return ans;
}
ll b_search(int s, int e, int k) {
    ll l=-1e9, r=1e9+1;
    while (l<r) {
        ll m=l+r>>1;
        if (ask(s,e,1,n,1,m)<k) l=m+1;
        else r=m;
    }
    return l;
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n>>m; for (int i=1; i<=n; i++) cin>>a[i];
    build(1,n,1);
    for (int i=0; i<m; i++) {
        int l,r,k; cin>>l>>r>>k;
        cout<<b_search(l,r,k)<<'\n';
    }
    return 0;
}

建树复杂度:遍历了logn层,每层的merge函数累积遍历n个元素
查询复杂度:mloglog(e-s)

感觉比按双字段排序还要慢一点...

posted @ 2020-11-05 09:09  童年の波鞋  阅读(74)  评论(0编辑  收藏  举报