1. 程式人生 > 其它 >2022.3.18#洛谷P3834 可持久化線段樹

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 }