2022.3.18#洛谷P3834 可持久化线段树

  1     //看了主席树,初步理解可持久化线段树,是用于查找不同区间,利用不同插入值的时候的线段树,
  2     //因为每个线段树查找都只能是【1,maxn】
  3     //所以如果要查找【l,r】,那么把【1,r】-【1,l-1】作为每个节点,这就是看作从r的线段树中剥夺了l-1的节点
  4     //实现方法是要用dt建树(动态),在最开始的树上添加链条,这样不要造新树,毕竟o【4*n】
  5     //权值和二分都是【1,maxn】查询第k小的必要方法
  6     //题目对应:洛谷p3834
  7     //维护排序后元素的出现次数,利用前缀和得到第k小
  8 
  9 #include<cstdio>
 10 #include<cstring>
 11 #include<algorithm>
 12 #define mid (l+r)/2
 13 using namespace std;
 14 
 15 const int N = 200010;
 16 int n, q, m, cnt = 0;
 17 int a[N], b[N], T[N];//a:初始数组
 18 //b:排序后数组,使用其下标作为线段树叶子结点
 19 //T:第几棵树的root
 20 int sum[N<<5], L[N<<5], R[N<<5];
 21 //sum代表该节点区间的叶子数
 22 inline int build(int l, int r)//建造一个n大小的线段树
 23 {
 24     int rt = ++ cnt;//这里记录的是第几个节点,我们要把第几个节点和代表区间清楚分开!
 25     sum[rt] = 0;//初始建树,都是0
 26     if (l < r)
 27     {
 28         L[rt] = build(l, mid);//左右遍历
 29         R[rt] = build(mid+1, r);
 30     }
 31     return rt;
 32 }
 33 
 34 inline int update(int pre, int l, int r, int x)//其实也就是动态开点的过程
 35 {
 36     int rt = ++ cnt;//这里记录的是第几个节点,我们要把第几个节点和代表区间清楚分开!
 37 
 38     L[rt] = L[pre];
 39     R[rt] = R[pre]; 
 40     sum[rt] = sum[pre]+1;
 41 
 42     if (l < r)//找到x这个叶子,一路上包含他的都+1即可
 43     {
 44         if (x <= mid)//寻找节点所在左右树,和query不同,因为x是叶子结点的数,也就是区间中的一个数
 45         L[rt] = update(L[pre], l, mid, x);
 46         else 
 47         R[rt] = update(R[pre], mid+1, r, x);
 48     }
 49     return rt;
 50 }
 51 
 52 inline int query(int u, int v, int l, int r, int k)
 53 {
 54     if (l >= r)
 55         return l;
 56     int leftsum = sum[L[v]] - sum[L[u]];//剥夺操作
 57     //与quanzhi—--erfen_line_tree中
 58     //k和tree[root*2]比较异曲同工
 59     if (k<=leftsum) 
 60         return query(L[u], L[v], l, mid, k);
 61     else 
 62         return query(R[u], R[v], mid+1, r, k-leftsum);
 63 }
 64 
 65 int main()
 66 {
 67     scanf("%d%d", &n, &q);
 68     for (int i = 1; i <= n; i ++)
 69     {
 70         scanf("%d", &a[i]);
 71         b[i] = a[i];
 72     }//输入所有的数
 73     sort(b+1, b+1+n);//因为权值线段树需要排序的
 74     m = unique(b+1, b+1+n)-b-1;//这是为了得到一共有多少不重复的值,这是叶子结点数量,也是m大小的初始线段树
 75     //unique的返回值是第一个重复的所在,也是最后一位的后一位
 76     T[0] = build(1, m);
 77     //初始造树,之后每一颗树都是采取动态将多的需要的链接上去,来减少空间使用。
 78     //至于为什么要分开?不始终那棵树都要4*n的空间吗?
 79     //是的,初始树是4*n,但是可持久化需要m棵树,如果都新建树,那么将要m*4*n的空间
 80     //对后来的树使用【动态开点】可以将每次添加的空间减小为O(m*logn)
 81     //这样一共就【4*n+(m-1)*logn】
 82     //这解答了我2022.3.14日一整晚对于线段树动态开点的困惑,动态开点是用于多树情况的
 83 
 84     for (int i = 1; i <= n; i ++){
 85 
 86         int t = lower_bound(b+1, b+1+m, a[i])-b;//寻找元素在b中的排序
 87         //将使用b的序号建树,可以减少空余的叶子结点
 88         //返回的时候返回b【t】即可
 89         T[i] = update(T[i-1], 1, m, t);
 90         //T代表第几棵树,在上一棵树的基础上添加链,T的值代表
 91         //该棵树的root值
 92     }
 93 
 94     while (q --){
 95         int x, y, z;
 96         scanf("%d%d%d", &x, &y, &z);
 97         int t = query(T[x-1], T[y], 1, m, z);//寻找【x,y】即【1,y】-【1,x-1】
 98         printf("%d\n", b[t]);
 99     }
100     return 0;
101 }

 

posted @ 2022-03-18 10:26  Tiachi  阅读(59)  评论(0)    收藏  举报