高贵冷艳的主席树……
主席树是什么
是一种数据结构
主席树对于序列$1..i$的每一个前缀各建一颗值域线段树。
很重要的是主席树具有可减性,这是它能够区间操作的重要前提,这有点类似于前缀和的思想。
好的假设我们现在建出了这$n$颗线段树,那么我们的确是可以进行各种区间操作了。但是每颗线段树的空间是$maxn<<4$啊,空间复杂度显然要出事。
回到最初主席树的实现上来:1.它对于前缀建树;2.它建的是值域线段树。
性质一:对于前缀建树
那么相邻之间的两颗树的重复信息是很多的嘛,每次修改$logn$的节点信息,于是我们可以共享其余相同的节点。
性质二:建的是值域线段树
显然值域线段树的构造形态是和所处位置无关的,换句话说就是这$n$颗值域线段树的构造是相同的,只是其节点的$val$不一样。这一点保证了主席树共享节点后的空间复杂度的正确性。
至于如何共享节点,其实也没有什么刁钻的操作。我们只要动态开点,并且继承上一次的节点信息就好了。
以上是静态主席树的大致内容,挂一篇好博客:题解 P3834 【【模板】可持久化线段树 1(主席树)】
主席树的难点实际在于应用而不是其理解(怎么和网络流一样)。
例题
P3834 【模板】可持久化线段树 1(主席树)
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值。
输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
题目分析
挂代码:
1 #include<bits/stdc++.h> 2 const int maxn = 200035; 3 4 struct node 5 { 6 int l,r,val,root; 7 }a[maxn<<5]; 8 int tot,n,m,q; 9 int x[maxn],b[maxn]; 10 11 int read() 12 { 13 char ch = getchar(); 14 int num = 0; 15 bool fl = 0; 16 for (; !isdigit(ch); ch = getchar()) 17 if (ch=='-') fl = 1; 18 for (; isdigit(ch); ch = getchar()) 19 num = (num<<1)+(num<<3)+ch-48; 20 if (fl) num = -num; 21 return num; 22 } 23 int build(int l, int r) 24 { 25 if (l==r) return ++tot; 26 int rt = ++tot, mid = (l+r)>>1; 27 a[rt].l = build(l, mid); 28 a[rt].r = build(mid+1, r); 29 return rt; 30 } 31 int update(int pre, int l, int r, int x) 32 { 33 int rt = ++tot, mid = (l+r)>>1; 34 a[rt] = a[pre]; 35 a[rt].val++; 36 if (l < r){ 37 if (x <= mid) a[rt].l = update(a[pre].l, l, mid, x); 38 else a[rt].r = update(a[pre].r, mid+1, r, x); 39 } 40 return rt; 41 } 42 int query(int L, int R, int l, int r, int k) 43 { 44 if (l==r) return l; 45 int val = a[a[R].l].val-a[a[L].l].val, mid = (l+r)>>1; 46 if (k <= val) return query(a[L].l, a[R].l, l, mid, k); 47 return query(a[L].r, a[R].r, mid+1, r, k-val); 48 } 49 int main() 50 { 51 n = read(), q = read(); 52 for (int i=1; i<=n; i++) x[i] = b[i] = read(); 53 std::sort(b+1, b+n+1); 54 m = std::unique(b+1, b+n+1)-b-1; 55 a[0].root = build(1, m); 56 for (int i=1; i<=n; i++) 57 { 58 int tt = std::lower_bound(b+1, b+m+1, x[i])-b; 59 a[i].root = update(a[i-1].root, 1, m, tt); 60 } 61 for (int i=1; i<=q; i++) 62 { 63 int l = read(), r = read(), k = read(); 64 printf("%d\n",b[query(a[l-1].root, a[r].root, 1, m, k)]); 65 } 66 return 0; 67 }
「二分」#2555. 「CTSC2018」混合果汁
题目描述
小 R 热衷于做黑暗料理,尤其是混合果汁。
商店里有 nnn 种果汁,编号为 0,1,2,...,n−1。iii 号果汁的美味度是 did_idi,每升价格为 pip_ipi。小 R 在制作混合果汁时,还有一些特殊的规定,即在一瓶混合果汁中,iii 号果汁最多只能添加 lil_ili 升。
现在有 mmm 个小朋友过来找小 R 要混合果汁喝,他们都希望小 R 用商店里的果汁制作成一瓶混合果汁。其中,第 jjj 个小朋友希望他得到的混合果汁总价格不大于 gjg_jgj,体积不小于 LjL_jLj。在上述这些限制条件下,小朋友们还希望混合果汁的美味度尽可能地高,一瓶混合果汁的美味度等于所有参与混合的果汁的美味度的最小值。请你计算每个小朋友能喝到的最美味的混合果汁的美味度。
输入格式
输入第一行包含两个正整数 n,mn, mn,m,表示果汁的种数和小朋友的数量。接下来 nnn 行,每行三个正整数 di,pi,lid_i, p_i, l_idi,pi,li,表示 iii 号果汁的美味度为 did_idi,每升价格为pip_ipi,在一瓶果汁中的添加上限为 lil_ili。
接下来 mmm 行依次描述所有小朋友:每行两个数正整数 gj,Ljg_j, L_jgj,Lj 描述一个小朋友,表示他最多能支付 gjg_jgj 元钱,他想要至少 LjL_jLj 升果汁。
输出格式
对于所有小朋友依次输出:对于每个小朋友,输出一行,包含一个整数,表示他能喝到的最美味的混合果汁的美味度。如果无法满足他的需求,则输出 −1。
数据范围与提示
对于所有的测试数据,保证n,m≤100000,1≤di,pi,li≤105,1≤gj,Lj≤10^18。
题目分析
主席树上二分
一般来说呢,这些有关数列的值的操作总是可以用主席树维护的。然后这题求的是最小值最大化,于是二分判断就好啦。
%%%jyz代码又简洁又跑得快
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 const int maxn = 100035; 4 5 struct node 6 { 7 int d,p,l; 8 bool operator < (node a) const 9 { 10 return d<a.d; 11 } 12 }a[maxn]; 13 int n,m,mx; 14 int rt[maxn],ls[maxn<<7],rs[maxn<<7],tot; 15 ll num[maxn<<7],sum[maxn<<7]; 16 17 ll read() 18 { 19 ll num = 0; 20 char ch = getchar(); 21 bool fl = 0; 22 for (; !isdigit(ch); ch = getchar()) 23 if (ch=='-') fl = 1; 24 for (; isdigit(ch); ch = getchar()) 25 num = (num<<1)+(num<<3)+ch-48; 26 if (fl) num = -num; 27 return num; 28 } 29 int update(int pre, int l, int r, int u, int v) 30 { 31 int nd = ++tot, mid = (l+r)>>1; 32 ls[nd] = ls[pre], rs[nd] = rs[pre]; 33 sum[nd] = sum[pre]+1ll*u*v; 34 num[nd] = num[pre]+v; 35 if (l < r){ 36 if (u <= mid) ls[nd] = update(ls[pre], l, mid, u, v); 37 else rs[nd] = update(rs[pre], mid+1, r, u, v); 38 } 39 return nd; 40 } 41 ll query(int nd, int l, int r, int k) 42 { 43 if (l==r) return 1ll*l*k; 44 int mid = (l+r)>>1; 45 if (k > num[ls[nd]]) return sum[ls[nd]]+query(rs[nd], mid+1, r, k-num[ls[nd]]); 46 return query(ls[nd], l, mid, k); 47 } 48 int main() 49 { 50 n = read(), m = read(); 51 for (int i=1; i<=n; i++) 52 a[i].d = read(), a[i].p = read(), a[i].l = read(), 53 mx = a[i].p > mx?a[i].p:mx; 54 std::sort(a+1, a+n+1); 55 mx++; 56 for (int i=n; i; i--) 57 rt[i] = update(rt[i+1], 1, mx, a[i].p, a[i].l); 58 for (int i=1; i<=m; i++) 59 { 60 ll g = read(), l = read(); 61 if (sum[rt[1]] < l||query(rt[1], 1, mx, l)>g){ 62 printf("-1\n"); 63 continue; 64 } 65 int lf = 1, rf = n, mid; 66 while (lf <= rf) 67 { 68 mid = (lf+rf)>>1; 69 if (num[rt[mid]]>=l&&query(rt[mid], 1, mx, l)<=g) 70 lf = mid+1; 71 else rf = mid-1; 72 } 73 printf("%d\n",a[rf].d); 74 } 75 return 0; 76 }
「差分」3932: [CQOI2015]任务查询系统
Description
Input
对于第一次查询,Pre=1。
Output
HINT
题目分析
我们可以用类似于差分的思想,把每条线段拆成两个端点,再按照优先级建一颗主席树。
然后预处理一下每一个时间点应该查询哪一个端点,于是就好了。
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 const int maxNode = 200035; 4 const int maxn = maxNode*40; 5 6 struct node 7 { 8 ll pos,val; 9 bool operator < (node a) const 10 { 11 return pos < a.pos; 12 } 13 node() {} 14 node(ll a, ll b):pos(a),val(b) {} 15 }a[maxNode]; 16 int n,m,cnt,tot; 17 ll pre,mx,rt[maxNode],ls[maxn],rs[maxn]; 18 ll size[maxn],sum[maxn],to[maxNode]; 19 20 ll read() 21 { 22 ll num = 0; 23 char ch = getchar(); 24 bool fl = 0; 25 for (; !isdigit(ch); ch = getchar()) 26 if (ch=='-') fl = 1; 27 for (; isdigit(ch); ch = getchar()) 28 num = (num<<1)+(num<<3)+ch-48; 29 if (fl) num = -num; 30 return num; 31 } 32 void pushup(int x) 33 { 34 size[x] = size[ls[x]]+size[rs[x]]; 35 sum[x] = sum[ls[x]]+sum[rs[x]]; 36 } 37 ll update(ll pre, ll l, ll r, ll c) 38 { 39 ll nd = ++tot, mid = (l+r)>>1; 40 ls[nd] = ls[pre], rs[nd] = rs[pre]; 41 if (l==r){ 42 if (c > 0) 43 size[nd] = size[pre]+1; 44 else size[nd] = size[pre]-1; 45 sum[nd] = sum[pre]+c; 46 return nd; 47 } 48 if (abs(c) <= mid) 49 ls[nd] = update(ls[pre], l, mid, c); 50 else rs[nd] = update(rs[pre], mid+1, r, c); 51 pushup(nd); 52 return nd; 53 } 54 ll check(ll x, ll k) 55 { 56 ll nd = rt[x], ret = 0, l = 1, r = mx, mid; 57 if (size[nd] <= k) return sum[nd]; 58 while (l < r) 59 { 60 mid = (l+r)>>1; 61 if (size[ls[nd]] >= k){ 62 r = mid; 63 nd = ls[nd]; 64 }else{ 65 l = mid+1; 66 ret += sum[ls[nd]]; 67 k -= size[ls[nd]]; 68 nd = rs[nd]; 69 } 70 } 71 return ret += l*k; 72 } 73 int main() 74 { 75 m = read(), n = read(); 76 for (int i=1; i<=m; i++) 77 { 78 ll u = read(), v = read(), w = read(); 79 a[++cnt] = node(u, w); 80 a[++cnt] = node(v+1, -w); 81 mx = mx < w+1?w+1:mx; 82 } 83 std::sort(a+1, a+cnt+1); 84 for (int i=1; i<=cnt; i++) 85 rt[i] = update(rt[i-1], 1, mx, a[i].val); 86 for (int i=cnt; i; i--) 87 if (a[i].pos!=a[i+1].pos) 88 to[a[i].pos] = i; 89 for (int i=1; i<=n; i++) 90 if (!to[i]) to[i] = to[i-1]; 91 pre = 1; 92 for (int i=1; i<=n; i++) 93 { 94 ll x = read(), a = read(), b = read(), c = read(), k = 1+(a*pre+b)%c; 95 pre = check(to[x], k); 96 printf("%lld\n",pre); 97 } 98 return 0; 99 }
「套树状数组」bzoj1901: Zju2112 Dynamic Rankings
Description
Input
Output
对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。
题目分析
动态区间第k大的模板题(感动,bzoj居然还有板子题)
我们在外层套树状数组就好了
注意一下细节不要打挂(其实还是要多练练熟了就好了)
1 #include<bits/stdc++.h> 2 const int maxn = 10013; 3 4 struct node 5 { 6 int i,j,k; 7 }qr[maxn]; 8 int n,m; 9 int v[maxn],col[maxn<<1],tot,cnt; 10 int rt[maxn],ls[maxn<<8],rs[maxn<<8],val[maxn<<8]; 11 int cntl,cntr,L[31],R[31]; 12 char cmd[13]; 13 14 int read() 15 { 16 int num = 0; 17 char ch = getchar(); 18 bool fl = 0; 19 for (; !isdigit(ch); ch = getchar()) 20 if (ch=='-') fl = 1; 21 for (; isdigit(ch); ch = getchar()) 22 num = (num<<1)+(num<<3)+ch-48; 23 if (fl) num = -num; 24 return num; 25 } 26 inline int lowbit(int x){return x&-x;} 27 int update(int pre, int l, int r, int x, int c) 28 { 29 int nd = ++tot, mid = (l+r)>>1; 30 ls[nd] = ls[pre], rs[nd] = rs[pre]; 31 val[nd] = val[pre]+c; 32 if (l==r) return nd; 33 if (x <= mid) 34 ls[nd] = update(ls[pre], l, mid, x, c); 35 else rs[nd] = update(rs[pre], mid+1, r, x, c); 36 return nd; 37 } 38 int query(int l, int r, int k) 39 { 40 if (l==r) return l; 41 int suml = 0, sumr = 0, mid = (l+r)>>1; 42 for (int i=1; i<=cntl; i++) suml += val[ls[L[i]]]; 43 for (int i=1; i<=cntr; i++) sumr += val[ls[R[i]]]; 44 if (sumr-suml >= k){ 45 for (int i=1; i<=cntl; i++) L[i] = ls[L[i]]; 46 for (int i=1; i<=cntr; i++) R[i] = ls[R[i]]; 47 return query(l, mid, k); 48 }else{ 49 for (int i=1; i<=cntl; i++) L[i] = rs[L[i]]; 50 for (int i=1; i<=cntr; i++) R[i] = rs[R[i]]; 51 return query(mid+1, r, k-(sumr-suml)); 52 } 53 } 54 int main() 55 { 56 n = read(), m = read(); 57 for (int i=1; i<=n; i++) 58 v[i] = col[++cnt] = read(); 59 for (int i=1; i<=m; i++) 60 { 61 scanf("%s",cmd); 62 qr[i].i = read(), qr[i].j = read(); 63 if (cmd[0]=='Q') qr[i].k = read(); 64 else col[++cnt] = qr[i].j; 65 } 66 std::sort(col+1, col+cnt+1); 67 cnt = std::unique(col+1, col+cnt+1)-col-1; 68 for (int i=1; i<=n; i++) 69 { 70 int tt = std::lower_bound(col+1, col+cnt+1, v[i])-col; 71 for (int j=i; j<=n; j+=lowbit(j)) 72 rt[j] = update(rt[j], 1, cnt, tt, 1); 73 } 74 for (int i=1; i<=m; i++) 75 if (qr[i].k){ 76 cntl = 0, cntr = 0; 77 qr[i].i--; 78 for (int j=qr[i].i; j; j-=lowbit(j)) L[++cntl] = rt[j]; 79 for (int j=qr[i].j; j; j-=lowbit(j)) R[++cntr] = rt[j]; 80 printf("%d\n",col[query(1, cnt, qr[i].k)]); 81 }else{ 82 int tt = std::lower_bound(col+1, col+cnt+1, v[qr[i].i])-col; 83 for (int j=qr[i].i; j<=n; j+=lowbit(j)) 84 rt[j] = update(rt[j], 1, cnt, tt, -1); 85 v[qr[i].i] = qr[i].j; 86 tt = std::lower_bound(col+1, col+cnt+1, v[qr[i].i])-col; 87 for (int j=qr[i].i; j<=n; j+=lowbit(j)) 88 rt[j] = update(rt[j], 1, cnt, tt, 1); 89 } 90 return 0; 91 }
END