樹套樹總結
阿新 • • 發佈:2021-06-12
最近做題發現自己並不知道什麼時候該用樹套樹,就來總結一下
一、靜態整體kth
排序輸出
sort(a+1,a+n+1);
printf("%d\n",a[k]);
時間複雜度O(nlogn) 空間複雜度O(n)
二、動態整體kth
權值線段樹+二分
查詢時先查詢左子樹和sum,比較k和sum的大小:若k<=sum則說明第k小數在左子樹中,遞迴查詢左子樹;
否則,這個數對應的就是右子樹中第k-sum小的數,k-=sum,遞迴查詢右子樹。
時間複雜度O(nlogn) 空間複雜度O(n)
應該是這麼寫的吧……甚至離散化剛開始都寫錯了
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; int read(){ int x = 1,a = 0;char ch = getchar(); while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();} while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();} return x*a; } const int maxn = 1e5+10; int sum[maxn << 1]; int ls(int x){return x << 1;} int rs(int x){return x << 1 | 1;} void modify(int x,int l,int r,int p,int k){ sum[x] += k; if (l == r) return; int mid = (l+r >> 1); if (p <= mid) modify(ls(x),l,mid,p,k); else modify(rs(x),mid+1,r,p,k); } int query(int x,int l,int r,int k){ // cout<<l<<" "<<r<<" "<<sum[ls(x)]<<" "<<k<<endl; int mid = (l+r >> 1); if (l == r) return l; if (sum[ls(x)] >= k) return query(ls(x),l,mid,k); else return query(rs(x),mid+1,r,k-sum[ls(x)]); } int n,m; int a[maxn],b[maxn]; int main(){ n = read(),m = read(); for (int i = 1;i <= n;i++) a[i] = b[i] = read(); 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; for (int i = 1;i <= n;i++) modify(1,1,len,a[i],1); for (int i = 1;i <= m;i++){ int op = read(); if (op == 0){ int x = read(),k = read(); modify(1,1,len,a[x],-1); a[x] = k; modify(1,1,len,a[x],1); } if (op == 1){ int k = read(); printf("%d\n",b[query(1,1,len,k)]); } } return 0; }
三、靜態區間kth
對每個點以其字首開一棵權值線段樹,那麼任意一段區間均可以表示成為兩棵權值線段樹作差,即R位置的線段樹減去L-1位置上的線段樹
每個點開一棵線段樹空間複雜度\(O(n^2)\),MLE,考慮到後一個位置相比於前一個位置的更改只有logn個節點,所以使用主席樹
時間複雜度O(nlogn) 空間複雜度O(nlogn)
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; int read(){ int x = 1,a = 0;char ch = getchar(); while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();} while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();} return x*a; } const int maxn = 1e5+10; int sum[maxn << 1]; int tot,root[maxn]; struct node{ int ls,rs,val; }tree[maxn*30]; void modify(int &now,int lst,int l,int r,int p,int k){ if (!now) now = ++tot; tree[now].val = tree[lst].val + k; if (l == r) return; int mid = (l+r >> 1); if (p <= mid) tree[now].rs = tree[lst].rs,modify(tree[now].ls,tree[lst].ls,l,mid,p,k); else tree[now].ls = tree[lst].ls,modify(tree[now].rs,tree[lst].rs,mid+1,r,p,k); } int query(int now,int lst,int l,int r,int k){ if (!now) return 0; int mid = (l+r >> 1); if (l == r) return l; int res = tree[tree[now].ls].val-tree[tree[lst].ls].val; if (res >= k) return query(tree[now].ls,tree[lst].ls,l,mid,k); else return query(tree[now].rs,tree[lst].rs,mid+1,r,k-res); } int n,m; int a[maxn],b[maxn]; int main(){ n = read(),m = read(); for (int i = 1;i <= n;i++) a[i] = b[i] = read(); 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; for (int i = 1;i <= n;i++) modify(root[i],root[i-1],1,len,a[i],1); for (int i = 1;i <= m;i++){ int l = read(),r = read(),k = read(); printf("%d\n",b[query(root[r],root[l-1],1,len,k)]); } return 0; }
四、動態區間kth
還是要想辦法維護字首和。如果只是同3的字首和的話,就要對字首和進行O(nlogn)的單次修改,顯然TLE。
這裡考慮用樹狀陣列維護字首和。修改時,可以只修改logn個位置,複雜度\(O(log^2n)\)
查詢時,依舊是R位置減去L-1位置,這時候不再是兩棵線段樹作差,而是log棵線段樹與log棵線段樹作差;跳的時候,log個節點一起跳到左子樹/右子樹
時間複雜度\(O(nlog^2n)\) 空間複雜度O(nlogn)