可持久化线段树

可持久化权值线段树

· 又叫主席树

· 本质就是多棵线段树

· 可持久化表示可以维护历史任一版本的数据

· 例题

· Q1:给定 n 个整数构成的序列 ,需要支持两种操作

· 在某个历史版本上修改某一个位置上的值

· 访问某个历史版本上的某一位置的值

· A1: 保存每次插入操作时的历史版本.

· 对于每次操作都开一棵线段树

· 空间显然会爆,考虑优化

· 可以观察到,对于每次修改操作,最多更改 O(logn) 个节点(单点修改最多遍历 O(logn) 个节点)

· 因此对于每次修改,动态开点记录修改的状态。这样可以大大地减少冗余

· 注意存树一定要动态开点!!!

· Code:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int kmax = 1e6 + 3;

int n, m, a[kmax];
int rt[kmax];

struct Segment {
  struct Tree {
    int l, r, mid, s;
    int ls, rs;     //记录左右儿子
  } t[kmax << 5];
  int tc;

  Segment() {
    tc = 0;
  }

  int Build(int x, int l, int r) {
    t[x] = {l, r, (l + r) >> 1};
    if (l == r) {
      t[x].s = a[l];
      return x;
    }
    t[x].s = 0;
    t[x].ls = Build(++tc, l, t[x].mid);    //动态开点储存节点
    t[x].rs = Build(++tc, t[x].mid + 1, r);
    return x;
  }

  int Modify(int x, int k, int v) {
    t[++tc] = t[x];     //记录新版本
    x = tc;
    if (t[x].l == k && k == t[x].r) {
      t[x].s = v;
      return x;
    }
    if (k <= t[x].mid) {
      t[x].ls = Modify(t[x].ls, k, v);
    } else {
      t[x].rs = Modify(t[x].rs, k, v);
    }
    return x;
  }

  int Query(int x, int k) {
    if (t[x].l == t[x].r && t[x].l == k) {
      return t[x].s;
    }
    if (k <= t[x].mid) return Query(t[x].ls, k);
    return Query(t[x].rs, k);
  }

} tr;

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
  }
  rt[0] = tr.Build(++tr.tc, 1, n);
  for (int i = 1, tim, op, x, v; i <= m; i++) {
    scanf("%d%d%d", &tim, &op, &x);
    if (op == 1) {
      scanf("%d", &v);
      rt[i] = tr.Modify(rt[tim], x, v);
    } else {
      printf("%d\n", tr.Query(rt[tim], x));
      rt[i] = rt[tim];
    }
  }
  return 0;
}

· Q2:给定 n 个整数构成的序列 ,将对于指定的闭区间 [l,r] 查询其区间内的第 k 小值。

· A2: 简化问题,求 [1,p] 的第 k 小值。

· 对于简化问题,只需要找到插入 p 时的根节点版本,然后在普通权值线段树进行操作就行了

· 联系到 前缀和,只需要用 [1,r] 的结果减去 [1,l1] 的结果就行了。(显然主席树统计满足区间减法的性质)

· Code:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int kmax = 2e5 + 3;

struct Segment {
  int s;
  int ls, rs;
} t[kmax << 5];

int n, m, c, p, tc;
int a[kmax], b[kmax];
int rt[kmax];

void Build(int &x, int l, int r) {
  x = ++tc;
  if (l == r) return;
  int mid = (l + r) >> 1;
  Build(t[x].ls, l, mid);
  Build(t[x].rs, mid + 1, r);
}

int Modify(int x, int l, int r, int k) {
  int _x = ++tc;
  t[_x] = t[x];
  t[_x].s = t[x].s + 1;
  if (l == r) return _x;
  int mid = (l + r) >> 1;
  if (k <= mid) {
    t[_x].ls = Modify(t[_x].ls, l, mid, k);
  } else {
    t[_x].rs = Modify(t[_x].rs, mid + 1, r, k);
  }
  return _x;
}

int Query(int x, int y, int l, int r, int k) {
  if (l == r) return l;
  int d = t[t[y].ls].s - t[t[x].ls].s;
  int mid = (l + r) >> 1;
  if (d >= k) return Query(t[x].ls, t[y].ls, l, mid, k);
  return Query(t[x].rs, t[y].rs, mid + 1, r, k - d);
}

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    b[i] = a[i];
  }
  sort(b + 1, b + n + 1);
  c = unique(b + 1, b + n + 1) - b - 1;
  Build(rt[0], 1, c);
  for (int i = 1; i <= n; i++) {
    p = lower_bound(b + 1, b + c + 1, a[i]) - b;
    rt[i] = Modify(rt[i - 1], 1, c, p);
  }
  for (int i = 1, l, r, k; i <= m; i++) {
    scanf("%d%d%d", &l, &r, &k);
    printf("%d\n", b[Query(rt[l - 1], rt[r], 1, c, k)]);
  }
  return 0;
}

posted @   ereoth  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示