权值线段树&树状数组实现求第k大&前驱&后继

题面

普通平衡树
在这里插入图片描述
在这里插入图片描述

树状数组思路

这题其实是平衡树的板子…;

用树状数组做需要离线离散化处理;


首先增加删除一个数很容易,直接用树状数组的加一减一即可;


如果要查询数值 x x x的排名

其实相当于问我们1到x-1有多少个数

那么query后加一即可;


如果要查第 k k k小的数,这个和往常的树状数组不同,要写一个新的函数;

有两种理解方式,一种是类似倍增LCA的想法;

另一种是反向求和

比如说我们要 13 13 13

我们用二进制的思想去拼凑他, 13 = 8 + 4 + 1 13=8+4+1 13=8+4+1

核心思想就是找到一个小于它但是最大的二的次方数,然后不断的逼近他;

其实就是求一个前缀和,因为前缀和就是排名;

比如求 13 13 13就像下图一样
在这里插入图片描述

(其实这两都差不多)


接着要查询小于 x x x的最大的数以及查询大于 x x x的最小的数;

比如说数列为 1 , 2 , 2 , 3 , 4 , 4 1,2,2,3,4,4 1,2,2,3,4,4

我们要查小于 3 3 3的最大数,那么只需要用kth q u e r y ( 3 − 1 ) query(3-1) query(31)代入即可,也就是算出它多少名;

而大于 3 3 3的最小的数,我们同样用kth,代入的参数有点不同;

因为 4 4 4有多个,那么我们只需要求出包括 3 3 3在内有多少个数,再将其加一,便得到了排名;

Code

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int tr[N],n;

struct Query{
    int opt,x;
}q[N];

vector<int> ve;

int _get(int x){
    return lower_bound(ve.begin(),ve.end(),x) - ve.begin() + 1;
}
int lowbit(int x){
    return x & -x;
}
void add(int u,int v){
    for(int i=u;i<=ve.size();i+=lowbit(i)){
        tr[i] += v;
    }
}
int query(int x){
    int ret = 0;
    for(int i=x;i;i-=lowbit(i)){
        ret += tr[i];
    }
    return ret;
}
//返回排名为rank的值
int kth(int rank){
    int idx = 0;
    //反向求和的过程
    for(int i=20;i>=0;--i){
        idx += (1<<i);
        if(idx > ve.size() || tr[idx]>= rank){
            //类似倍增LCA,等于rank不一定是
            idx -= (1<<i);
        }else{
            rank -= tr[idx];
        }
    }
    //+1-1是因为vector从0开始
    //如果是从1开始的数组应该为idx+1
    return ve[idx+1-1];
}
int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> q[i].opt >> q[i].x;
        if(q[i].opt != 4) ve.push_back(q[i].x);
    }
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    for(int i=1;i<=n;++i){
        int op = q[i].opt,x = _get(q[i].x);
        if(op == 1){
            add(x,1);
        }else if(op == 2){
            add(x,-1);
        }else if(op == 3){
            cout << query(x-1) + 1 << '\n';
        }else if(op == 4){
            cout << kth(q[i].x) << '\n';
        }else if(op == 5){
            cout << kth(query(x-1)) << '\n';
        }else{
            cout << kth(query(x) + 1) << '\n';
        }
    }
    return 0;
}

线段树思路

其实就是权值线段树的想法,每个点不再代表区间,而是代表数值域上的点;

要注意下面这份代码与上面的树状数组不同;

数值val的排名上面返回的是最右边,我们这返回的是最左边

因此可以看到调用的时候参数传递的不同;

Code

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n;

struct Query{
    int opt,x;
}q[N];

struct Node{
    int l,r,sum;
}tr[N<<2];

vector<int> ve;

#define lc (p<<1)
#define rc (p<<1|1)

void build(int p,int l,int r){
    tr[p] = {l,r,0};
    if(l == r){
        return;
    }
    int mid = (l+r) >> 1;
    build(lc,l,mid);
    build(rc,mid+1,r);
}
void push_up(int p){
    tr[p].sum = tr[lc].sum + tr[rc].sum;
}
//单点加减
void node_add(int p,int k,int val){
    if(tr[p].l == tr[p].r){
        tr[p].sum += val;
        return;
    }
    int mid = (tr[p].l+tr[p].r) >> 1;
    if(k<=mid) node_add(lc,k,val);
    else node_add(rc,k,val);
    push_up(p);
}
//数值val的排名
int val_kth(int p,int val){
    if(tr[p].l == tr[p].r) return 1;
    int mid = (tr[p].l+tr[p].r) >> 1;
    if(val<=mid) return val_kth(lc,val);
    return tr[lc].sum + val_kth(rc,val);
}
//排名为k的数值
int kth(int p,int k){
    if(tr[p].l == tr[p].r) return tr[p].l;
    if(tr[lc].sum>=k) return kth(lc,k);
    return kth(rc,k-tr[lc].sum);
}

int _get(int x){
    return lower_bound(ve.begin(),ve.end(),x) - ve.begin() + 1;
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> q[i].opt >> q[i].x;
        if(q[i].opt != 4) ve.push_back(q[i].x);
    }
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    build(1,1,ve.size());
    for(int i=1;i<=n;++i){
        int op = q[i].opt,x = _get(q[i].x);
        if(op == 1){
            node_add(1,x,1);
        }else if(op == 2){
            node_add(1,x,-1);
        }else if(op == 3){
            cout << val_kth(1,x) << '\n';
        }else if(op == 4){
            cout << ve[-1+kth(1,q[i].x)] << '\n';
        }else if(op == 5){
            cout << ve[-1+kth(1,val_kth(1,x)-1)] << '\n';
        }else{
            cout << ve[-1+kth(1,val_kth(1,x+1))] << '\n';
        }
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值