主席树学习笔记

假设你已经学会了可持久化……

例题

给定 n n n 个数, m m m 次静态询问区间第 k k k 大。 1 ≤ n , m ≤ 1 0 5 1\le n,m\le 10^5 1n,m105

思想

先考虑 l = 1 l=1 l=1 r = n r=n r=n 的特殊情况。容易发现可以用一棵线段树来维护。

l = 1 l=1 l=1 r ∈ [ 1 , n ] r\in [1,n] r[1,n] 的时候,可以建立 n n n 棵线段树,但是修改很小,使用可持久化。

l l l r r r 取任一值, l ≤ r l\le r lr 的时候,运用前缀和的思想, [ l , r ] = [ 1 , r ] − [ 1 , l − 1 ] [l,r] = [1,r] - [1,l-1] [l,r]=[1,r][1,l1],用上一部的 n n n 棵线段树即可。

时间复杂度 O ( n × log ⁡ n ) O(n\times \log n) O(n×logn)


模板

注意是求第 k k k

但是值域线段树是开不下 1 0 9 10^9 109 的。

方法 1 1 1:离散化。

方法 2 2 2:动态插入。好写。但是有的时候不能用。

采用方法 2 2 2

#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long

using namespace std;

const int N = 2e5 + 10;

struct Node {
  int l, r, sum;
} z[N * 50];
int cnt;
int a[N], root[N];

void update(int p) {
  z[p].sum = z[z[p].l].sum + z[z[p].r].sum;
}

// 当前在p对应的区间是l~r 把i位置+1
// 返回修改后的新的根节点
int modify(int p, int l, int r, int i) {
  int pp;
  z[pp = ++ cnt] = z[p];
  if (l == r) {
    z[pp].sum ++;
    return pp;
  }
  int mid = l + r >> 1;
  if (i <= mid)
    z[pp].l = modify(z[p].l, l, mid, i);
  else
    z[pp].r = modify(z[p].r, mid + 1, r, i);
  update(pp);
  return pp;
}

int query(int rootl, int rootr, int l, int r, int k) {
  if (l == r)
    return l;
  int mid = l + r >> 1;
  int t = z[z[rootr].r].sum - z[z[rootl].r].sum;
  if (t >= k)
    return query(z[rootl].r, z[rootr].r, mid + 1, r, k);
  else
    return query(z[rootl].l, z[rootr].l, l, mid, k - t);
}

signed main() {
  int n, k = -1e9, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i ++)
    cin >> a[i];
  for (int i = 1; i <= n; i ++)
    a[i] += 1000000000;
  for (int i = 1; i <= n; i ++)
    k = max(k, a[i]);
  for (int i = 1; i <= n; i ++)
    root[i] = modify(root[i - 1], 1, k, a[i]);
  // root[i] --> a1 ~ ai
  while (m --) {
    int l, r, p;
    cin >> l >> r >> p;
    cout << query(root[l - 1], root[r], 1, k, r - l - p + 2) - 1000000000 << '\n';
  }
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值