动态主席树【带修改】&& 例题 Dynamic Rankings ZOJ - 2112
参考链接:https://blog.csdn.net/WilliamSun0122/article/details/77885781
一、动态主席树介绍
动态主席树与静态主席树的不同在于:静态主席树不能支持后期对区间内数的修改。
例如:刚开始区间内的数为1、4、3、2、5.你再建完主席树之后要把第二个位置的4改成2。这在原来的静态主席树上是没法操作的。只有重新在建一棵静态主席树
但是动态主席树可以借助树状数组和主席树 【权值线段树】来支持修改操作(也就是树套树了)
建议没有学过树状数组和主席树 【权值线段树】可以先学习一下
二、动态主席树构造
动态主席树与静态主席树在代码上的区别就在于,我们用树状数组又维护了一批权值线段树。用树状数组维护的权值线段树和静态主席树维护的权值线段树一起保证着每一个区间对应权值的正确性
以例题:Dynamic Rankings ZOJ - 2112的样例来解释一下
5 3
3 2 1 4 7
Q 1 4 3 询问区间[1,4]第3小数
C 2 6 把第2个数变为6
Q 2 5 3 询问区间[2,5]第3小数
介绍一下代码中各变量作用:
//T[i]表示第i棵线段树的根节点编号
//S[i]表示树状数组思维建的第i棵线段树的根节点编号
//L[i]表示节点i的左子节点编号
//R[i]表示节点i的右子节点编号
//sum[i]表示节点i对应区间中数的个数。
//origin[i]存放输入的n个数据
//v[i]存放输入的n个数据,以及后面要修改后的数据的值。要对他们进行排序、去重操作。

为了保证我们可以进行修改操作,我们新建一批权值线段树来记录更新,这些线段树以树状数组的思维来维护。
一开始,S[0]、S[1]、S[2]、S[3]、S[4]、S[5] (注意一共有n+1个 即 0到n)(树状数组的每个节点)这些都与T[0]相同(也就是每个节点建了一棵空树)。
对于C 2 6 这个操作, 我们只需要减去一个2,加上一个5(为什么是5?因为我们是对权值线段树处理,那么我们要在树状数组维护的权值线段树加上6,就要加上离散化之后的值。减去那个2也是这个意思)。
这个更新我们按树状数组的思想更新,比如这里的减2,我们要从i=2(原序列中第2个数2在离散化后序列中的位置)即S[2]开始更新,并往上lowbit(i)直到大于5,这里我们会更新S[2]和S[4](因为在树状数组中S[2]和S[4]包含离散化之后的2)。
减2例图:
加5例图(因为加5操作是把2位置更新成6,所以树状数组还是要修改S[2],S[4]两棵树):
这里我们的更新操作只会对树状数组维护的权值线段树进行操作,对于那个静态权值线段树我们并不进行修改
这个样子在我们查询某个区间中的权值大小时,我们既要考虑静态线段树的区间对应权值,还要考虑树状数组维护的区间对应权值
怎么找?
对于静态主席树的我就不用说了,还按原来的方法
对于树状数组维护的权值线段树,要找区间[l,r]权值,我们可以找到[1,l-1]和[1,r]的权值,这个找法和树状数组正常找法一样。让[1,r]的权值减去[1,l-1]的权值就可以了
3、复杂度
n是原序列长度,q是q次询问
时间复杂度为单次lognlogn 共nlogn+qlognlogn 或者不用前缀和 (n+q)lognlogn 空间复杂度为 qlognlogn 或者不用前缀和 (q+n)logn*logn 用垃圾回收重复利用空间可以 去掉一个logn的空间 nlogn 因为树状数组没有必要在修改之后再用修改之前的总共就只要开nlogn的空间因为c数组n个就可以了每个一条顶到叶子的链logn的空间,可以直接使用或者回收利用
4、例题代码
1 /*动态主席树(带修改的主席树),求区间内第k大*/
2 #include<stdio.h>
3 #include<string.h>
4 #include<iostream>
5 #include<algorithm>
6 using namespace std;
7 const int maxn=6e4+10; //数组开小也会segmentation fault
8 const int maxm=1e4+10; //我之前开的5e4+10
9 int T[maxn],S[maxn],L[maxn*32],R[maxn*32],sum[maxn*32];
10 int origin[maxn],v[maxn];
11 int ul[maxn],ur[maxn];
12 int cnt,n,q,num;
13 //n是原序列个数 ,q是询问次数
14 //T[i]表示第i棵线段树的根节点编号
15 //S[i]表示树状数组思维建的第i棵线段树的根节点编号
16 //L[i]表示节点i的左子节点编号
17 //R[i]表示节点i的右子节点编号
18 //sum[i]表示节点i对应区间中数的个数。
19 //origin[i]存放输入的n个数据
20 //v[i]存放输入的n个数据,以及后面要修改后的数据的值。要对他们进行排序、去重操作。
21
22 //这个ur、ul是在查询函数query中和静态查询连在一起用的
23
24 struct Node //存放的是q次的询问,因为我们带修改的主席树也是要在v里面统计修改后的数据的值
25 {
26 int l,r,k;
27 bool flag;
28 }Q[maxm];
29 void build(int& rt,int l,int r) //就是普通的给一个区间建立一个线段树
30 {
31 rt=++cnt;
32 sum[rt]=0;
33 if(l==r) return;
34 int mid=(l+r)>>1;
35 build(L[rt],l,mid); //注意这里是小写的L,可不是1
36 //把它换成1结果对,但是交上去会出错segmentation fault
37 build(R[rt],mid+1,r);
38 }
39 void update(int &rt,int pre,int l,int r,int x,int val) //更新某一棵树上的某个位置的权值
40 {
41 rt=++cnt;
42 L[rt]=L[pre];
43 R[rt]=R[pre];
44 sum[rt]=sum[pre]+val;
45 if(l==r) return;
46 int mid=(l+r)>>1;
47 if(x<=mid) update(L[rt],L[pre],l,mid,x,val);
48 else update(R[rt],R[pre],mid+1,r,x,val);
49 }
50 int lowbit(int x)
51 {
52 return x&(-x);
53 }
54 void add(int x,int val)
55 {
56 int ans=lower_bound(v+1,v+num+1,origin[x])-v;
57 while(x<=n)
58 {
59 update(S[x],S[x],1,num,ans,val);
60 x+=lowbit(x);
61 }
62 }
63 int Sum(int x,int flag) //它求的是1-x这所有权值线段树某个区间权值之和
64 {
65 int ans=0;
66 while(x>0)
67 {
68 if(flag) ans+=sum[L[ur[x]]];
69 else ans+=sum[L[ul[x]]];
70 x-=lowbit(x);
71 }
72 return ans;
73 }
74 int query(int s,int e,int ts,int te,int l,int r,int k)
75 {
76 if(l==r) return l;
77 int mid=(l+r)>>1;
78 //因为我们用树状数组来维护的,所以在求对应静态数组的那个ans的时候,我们还要考虑一下树状数组维护的线段树的权值
79 int ans=Sum(e,1)-Sum(s,0)+sum[L[te]]-sum[L[ts]];
80 if(k<=ans)
81 {
82 for(int i=e;i;i-=lowbit(i)) ur[i]=L[ur[i]]; //因为区间是逐渐缩小的,所有我们对应
83 for(int i=s;i;i-=lowbit(i)) ul[i]=L[ul[i]];
84 return query(s,e,L[ts],L[te],l,mid,k);
85 }
86 else
87 {
88 for(int i=e;i;i-=lowbit(i)) ur[i]=R[ur[i]];
89 for(int i=s;i;i-=lowbit(i)) ul[i]=R[ul[i]];
90 return query(s,e,R[ts],R[te],mid+1,r,k-ans);
91 }
92 }
93 int main()
94 {
95 int t,m;
96 scanf("%d",&t);
97 while(t--)
98 {
99 char str[10];
100 num=0;
101 scanf("%d%d",&n,&q);
102 num=n;
103 for(int i=1;i<=n;++i)
104 scanf("%d",&origin[i]),v[i]=origin[i];
105 for(int i=1;i<=q;++i)
106 {
107 scanf("%s",str);
108 if(str[0]=='Q')
109 {
110 scanf("%d%d%d",&Q[i].l,&Q[i].r,&Q[i].k);
111 Q[i].flag=1;
112 }
113 else
114 {
115 scanf("%d%d",&Q[i].l,&Q[i].r);
116 v[++num]=Q[i].r;
117 Q[i].flag=0;
118 }
119 }
120 sort(v+1,v+1+num);
121 num=unique(v+1,v+1+num)-v-1;
122 printf("%d****\n",num);
123 cnt=0;
124 build(T[0],1,num);
125 for(int i=1;i<=n;++i)
126 update(T[i],T[i-1],1,num,lower_bound(v+1,v+1+num,origin[i])-v,1);
127 for(int i=1;i<=n;++i)
128 S[i]=T[0];
129 for(int i=1;i<=q;++i)
130 {
131 if(Q[i].flag)
132 {
133 for(int j=Q[i].r;j;j-=lowbit(j)) ur[j] = S[j];
134 for(int j=Q[i].l-1;j;j-=lowbit(j)) ul[j] = S[j];
135 printf("%d\n",v[query(Q[i].l-1,Q[i].r,T[Q[i].l-1],T[Q[i].r],1,num,Q[i].k)]);
136
137 }
138 else
139 {
140 add(Q[i].l,-1);
141 origin[Q[i].l]=Q[i].r;
142 add(Q[i].l,1);
143 }
144 }
145 }
146 return 0;
147 }
动态主席树【带修改】&& 例题 Dynamic Rankings ZOJ - 2112的更多相关文章
- 洛谷 P2617 Dynamic Rankings || ZOJ - 2112
写的让人看不懂,仅留作笔记 静态主席树,相当于前缀和套(可持久化方法构建的)值域线段树. 建树方法:记录前缀和的各位置的线段树的root.先建一个"第0棵线段树",是完整的(不需要 ...
- Dynamic Rankings ZOJ - 2112(主席树+树状数组)
The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with t ...
- 主席树初探--BZOJ1901: Zju2112 Dynamic Rankings
n<=10000的序列做m<=10000个操作:单点修改,查区间第k小. 所谓的主席树也就是一个值域线段树嘛..不过在这里还是%%fotile 需要做一个区间查询,由于查第k小,需要一些能 ...
- LuoguP2617 Dynamic Rankings (动态主席树学习理解)
题目地址 题目链接 题解 动态主席树的板子题.动态主席树其实和静态的有很大差别,虽然同样是n个根,但是节点并不能共用,每个根节点表示bit上的一段区间. 所以其实是个树套树的东西来着,外层是bit,内 ...
- HDU 4348 To the moon(主席树区间修改)
题意 给你一个区间,支持如下操作: 在一段区间内加上一个值,并生成一个历史版本 查询某个版本下一段区间内的和 回到一个历史版本上并舍弃之后的版本 做法 这就是主席树区间修改裸题啦QwQ 上一篇博客我讲 ...
- ZOJ -2112 Dynamic Rankings 主席树 待修改的区间第K大
Dynamic Rankings 带修改的区间第K大其实就是先和静态区间第K大的操作一样.先建立一颗主席树, 然后再在树状数组的每一个节点开线段树(其实也是主席树,共用节点), 每次修改的时候都按照树 ...
- ZOJ 2112 Dynamic Rankings(树状数组套主席树 可修改区间第k小)题解
题意:求区间第k小,节点可修改 思路:如果直接用静态第k小去做,显然我更改一个节点后,后面的树都要改,这个复杂度太高.那么我们想到树状数组思路,树状数组是求前缀和,那么我们可以用树状数组套主席树,求出 ...
- BZOJ.1901.Dynamic Rankings(树状数组套主席树(动态主席树))
题目链接 BZOJ 洛谷 区间第k小,我们可以想到主席树.然而这是静态的,怎么支持修改? 静态的主席树是利用前缀和+差分来求解的,那么对于每个位置上的每棵树看做一个点,拿树状数组更新. 还是树状数组的 ...
- ZOJ2112 BZOJ1901 Dynamic Rankings 树套树 带修改的区间第k小
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2112 树套树,线段树套splay或者主席树套树状数组,我抄了一下hzwer ...
随机推荐
- MySQL select if 查询最后一个主键 id
查询最后一个主键id SELECT IF(MAX(id) IS NULL, 0, MAX(id)) AS maxid FROM users; 查询最小的主键id SELECT IF(MIN(id) I ...
- myisam崩溃后发生损坏的概率比innodb高的原因
myisam崩溃后发生损坏的概率比innodb高的原因
- 关于SSRF与CSRF漏洞的解释
目录 SSRF服务端请求伪造(外网访问内网) 1.SSRF形成原因 2.利用SSRF漏洞的目的 3.SSRF漏洞的用途 4.SSRF漏洞的特性 实例 5.如何挖掘SSRF漏洞 6.常用SSRF去做什么 ...
- 单片机—Arduino UNO-R3—学习笔记001
连接方法 下载Arduino软件 安装完成打开如图所示 观察右下角的连接接口"Arduino Uno在COM(X)" 在工具-->端口-->选择之前查看的端口 即为连接 ...
- BAPI_GOODSMVT_CREATE的参数GOODSMVT_CODE的说明
BAPI_GOODSMVT_CREATE 的功能就是用于货物移动,其主要可以实现MB*事物的一些功能,其中该BAPI的参数 GOODSMVT_CODE就控制了对应哪个事物码的功能,下面给出该参数的值和 ...
- python生成器 递归
生成器 生成器:只要函数体内出现yield关键字,那么再执行函数就不会执行函数代码,会得到一个结果,该结果就是生成器 生成器就是迭代器 yield的功能 1.yield为我们提供了一种自定义迭 ...
- 【Android初级】如何实现一个“模拟后台下载”的加载效果(附源码)
在Android里面,后台的任务下载功能是非常常用的,比如在APP Store里面下载应用,下载应用时,需要跟用户进行交互,告诉用户当前正在下载以及下载完成等. 今天我将通过使用Android的原生控 ...
- JMeter性能测试9:阿里云服务器压测
第一步准备好jmeter脚本 第二步 将本地的jmeter脚本上传到阿里云进行验证 检验阿里云的jmeter是否能正常运行 将本地的脚本上传到阿里云使用xshell进入到该目录下 使用命令运行刚才上传 ...
- 一体化的Linux系统性能和使用活动监控工具–Sysstat
[转]原文出处: Tecmint-Kuldeep Sharma 译文出处:Linux Story-天寒 欢迎分享原创到伯乐头条 在监控系统资源.系统性能和使用活动方面,Sysstat的确是一个 ...
- Building a high performance JSON parser
Building a high performance JSON parser https://dave.cheney.net/high-performance-json.html