主席树模板 / 区间第k小 / p3834


前言

主席树的全称是可持久化权值线段树,关键词有两个,一个是可持久化,一个是权值线段树

主席树与普通线段树有不少区别,学主席树之前最好学一下权值线段树,

理解其蕴含的前缀存储思想

可以把权值线段树理解为一个桶,每个叶子节点leaf[k]存的是a[1, …, n]中,k出现的次数

因此在构建主席树之前应当将数据离散化处理一下,这里由dispersed()和find()完成

主席树与普通权值线段树的区别主要在于增加了可持久化功能,

具体表现为update()函数的动态开点功能(开出历史存档),

以及query对不同版本的树根查询(借鉴了权值线段树的前缀存储思想)

一、 例题 p3834

二、 思路及代码

1. 思路

很经典的主席树入门,直接套模板即可

2. 代码

代码如下 :

#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 2e5 + 7;
int root[maxn], id, len = 1;
int a[maxn], b[maxn];
int n, m;
struct ptree {
  int l, r;
  int cnt;  // 权重线段树
} t[maxn << 5];
int dispersed() {
  for (int i = 1; i <= n; i++) b[i] = a[i];
  sort(b + 1, b + n + 1);
  int len = unique(b + 1, b + n + 1) - b - 1;
  //   for (int i = 1; i <= n; i++)
  //   a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
  return len;
}
int find(int x) {
  int l = 0, r = len;
  while (l < r) {
    int mid = l + r >> 1;
    if (b[mid] >= x)
      r = mid;
    else
      l = mid + 1;
  }
  return l;
}
int build(int l, int r) {
  int cur = ++id;
  if (l == r) return cur;
  int mid = l + r >> 1;
  t[cur].l = build(l, mid);
  t[cur].r = build(mid + 1, r);
  return cur;
}
int update(int pre, int x, int l, int r) {
  int cur = ++id;
  int mid = l + r >> 1;
  t[cur] = t[pre], t[cur].cnt++;  // 权值++
  if (l < r)
    if (x <= mid)  // 子树权值更新
      t[cur].l = update(t[pre].l, x, l, mid);
    else
      t[cur].r = update(t[pre].r, x, mid + 1, r);
  return cur;
}
int query(int lt, int rt, int l, int r, int k) {
  if (l == r) return l;
  int tmp = t[t[rt].l].cnt - t[t[lt].l].cnt;  // 左子树的权重
  int mid = l + r >> 1;  // 即1 - tmp排名的节点均在左子树
  if (k <= tmp) return query(t[lt].l, t[rt].l, l, mid, k);
  return query(t[lt].r, t[rt].r, mid + 1, r, k - tmp);
}
int main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);

  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  len = dispersed();
  root[0] = build(1, len);  // 离线构建权重线段树
  for (int i = 1; i <= n; i++)
    root[i] = update(root[i - 1], find(a[i]), 1, len);

  while (m--) {
    int l, r, k;
    scanf("%d%d%d", &l, &r, &k);  // 在线查询第k小
    printf("%d\n", b[query(root[l - 1], root[r], 1, len, k)]);
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值