初涉主席树

高贵冷艳的主席树……

主席树是什么

是一种数据结构

主席树对于序列$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,...,n1。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

最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的
任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行
),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向
查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个
)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先
级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。

Input

输入文件第一行包含两个空格分开的正整数m和n,分别表示任务总数和时间范围。接下来m行,每行包含三个空格
分开的正整数Si、Ei和Pi(Si≤Ei),描述一个任务。接下来n行,每行包含四个空格分开的整数Xi、Ai、Bi和Ci,
描述一次查询。查询的参数Ki需要由公式 Ki=1+(Ai*Pre+Bi) mod Ci计算得到。其中Pre表示上一次查询的结果,

对于第一次查询,Pre=1。

Output

输出共n行,每行一个整数,表示查询结果。

HINT

样例解释
K1 = (1*1+3)%2+1 = 1
K2 = (1*2+3)%4+1 = 2
K3 = (2*8+4)%3+1 = 3
对于100%的数据,1≤m,n,Si,Ei,Ci≤100000,0≤Ai,Bi≤100000,1≤Pi≤10000000,Xi为1到n的一个排列

题目分析

我们可以用类似于差分的思想,把每条线段拆成两个端点,再按照优先级建一颗主席树。

然后预处理一下每一个时间点应该查询哪一个端点,于是就好了。

 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

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1
],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改
变后的a继续回答上面的问题。

Input

第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。
分别表示序列的长度和指令的个数。
第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。
接下来的m行描述每条指令
每行的格式是下面两种格式中的一种。 
Q i j k 或者 C i t 
Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)
表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。
C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t
m,n≤10000

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

posted @ 2018-06-08 17:36  AntiQuality  阅读(186)  评论(0编辑  收藏  举报