参考链接: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小数

介绍一下代码中各变量作用:

//n是原序列个数 ,q是询问次数
//T[i]表示第i棵线段树的根节点编号
//S[i]表示树状数组思维建的第i棵线段树的根节点编号
//L[i]表示节点i的左子节点编号
//R[i]表示节点i的右子节点编号
//sum[i]表示节点i对应区间中数的个数。
//origin[i]存放输入的n个数据
//v[i]存放输入的n个数据,以及后面要修改后的数据的值。要对他们进行排序、去重操作。
//这个ur、ul是在查询函数query中和静态查询连在一起用的,这个具体看代码,比较难解释。
 
注意:
这里离散化建树过程和静态主席树有一点不同,我们必须把所有询问先存起来并且把改变的数也加入到原序列中再离散化建树
如上例:v[]数组里面的数应该是3、2、1、4、7、6
我们离散化后序列为3 2 1 4 6 5分别对应原序列的3 2 1 4 7和  改变后的6。
 
然后按照正常方法建立一颗静态主席树:

为了保证我们可以进行修改操作,我们新建一批权值线段树来记录更新,这些线段树以树状数组的思维来维护。
一开始,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的更多相关文章

  1. 洛谷 P2617 Dynamic Rankings || ZOJ - 2112

    写的让人看不懂,仅留作笔记 静态主席树,相当于前缀和套(可持久化方法构建的)值域线段树. 建树方法:记录前缀和的各位置的线段树的root.先建一个"第0棵线段树",是完整的(不需要 ...

  2. Dynamic Rankings ZOJ - 2112(主席树+树状数组)

    The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with t ...

  3. 主席树初探--BZOJ1901: Zju2112 Dynamic Rankings

    n<=10000的序列做m<=10000个操作:单点修改,查区间第k小. 所谓的主席树也就是一个值域线段树嘛..不过在这里还是%%fotile 需要做一个区间查询,由于查第k小,需要一些能 ...

  4. LuoguP2617 Dynamic Rankings (动态主席树学习理解)

    题目地址 题目链接 题解 动态主席树的板子题.动态主席树其实和静态的有很大差别,虽然同样是n个根,但是节点并不能共用,每个根节点表示bit上的一段区间. 所以其实是个树套树的东西来着,外层是bit,内 ...

  5. HDU 4348 To the moon(主席树区间修改)

    题意 给你一个区间,支持如下操作: 在一段区间内加上一个值,并生成一个历史版本 查询某个版本下一段区间内的和 回到一个历史版本上并舍弃之后的版本 做法 这就是主席树区间修改裸题啦QwQ 上一篇博客我讲 ...

  6. ZOJ -2112 Dynamic Rankings 主席树 待修改的区间第K大

    Dynamic Rankings 带修改的区间第K大其实就是先和静态区间第K大的操作一样.先建立一颗主席树, 然后再在树状数组的每一个节点开线段树(其实也是主席树,共用节点), 每次修改的时候都按照树 ...

  7. ZOJ 2112 Dynamic Rankings(树状数组套主席树 可修改区间第k小)题解

    题意:求区间第k小,节点可修改 思路:如果直接用静态第k小去做,显然我更改一个节点后,后面的树都要改,这个复杂度太高.那么我们想到树状数组思路,树状数组是求前缀和,那么我们可以用树状数组套主席树,求出 ...

  8. BZOJ.1901.Dynamic Rankings(树状数组套主席树(动态主席树))

    题目链接 BZOJ 洛谷 区间第k小,我们可以想到主席树.然而这是静态的,怎么支持修改? 静态的主席树是利用前缀和+差分来求解的,那么对于每个位置上的每棵树看做一个点,拿树状数组更新. 还是树状数组的 ...

  9. ZOJ2112 BZOJ1901 Dynamic Rankings 树套树 带修改的区间第k小

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2112 树套树,线段树套splay或者主席树套树状数组,我抄了一下hzwer ...

随机推荐

  1. .NET 5 程序高级调试-WinDbg

    上周和大家分享了.NET 5开源工作流框架elsa,程序跑起来后,想看一下后台线程的执行情况.抓了个进程Dump后,使用WinDbg调试,加载SOS调试器扩展,结果无法正常使用了: 0:000> ...

  2. TCP/IP协议栈在Linux内核中的运行时序分析

    网络程序设计调研报告 TCP/IP协议栈在Linux内核中的运行时序分析 姓名:柴浩宇 学号:SA20225105 班级:软设1班 2021年1月 调研要求 在深入理解Linux内核任务调度(中断处理 ...

  3. 如何构建一个多人(.io) Web 游戏,第 2 部分

    原文:How to Build a Multiplayer (.io) Web Game, Part 2 探索 .io 游戏背后的后端服务器. 上篇:如何构建一个多人(.io) Web 游戏,第 1 ...

  4. 电子邮箱、邮件地址、网站地址正则表达式!几个有用的RE、regex、regexp!

    几个常用的正则表达式! r"\w[-\w\.]*@\w[-\w]*(\.\w[-\w]*)+" 这个是电子邮件地址的. r"<TAG\b[^>]*<(. ...

  5. Oracle Rac to Rac One Node

    =~=~=~=~=~=~=~=~=~=~=~= PuTTY log 2020.01.14 20:05:12 =~=~=~=~=~=~=~=~=~=~=~= [oracle@rac01 ~]$ srvc ...

  6. Doge.jpg 的背后是什么,你知道么?

    图片,是我们生活中最常见的信息载体,作为一个日常生活中无处不在的事物,我们已经很习惯静态或者动态的图片了.大家也了解静态图片主要是jpg/png格式,动态图片主要为 gif.那你有没有过一瞬间的疑惑: ...

  7. 2V升3V芯片,输入2V输出3V可达1A

    PW5328B是一个恒定频率, 6引脚 SOT23电流模式升压转换器,用于小型低功耗应用. PW5328B的开关频率为 1.2MHz,允许使用微小的.低成本的电容器和电感器.内部软启动导致小涌流和延长 ...

  8. 迈凯伦765LT/600LT/720S/650S/570S维修手册电路图Mclaren车间手册接线图

    全套迈凯伦维修手册电路图Mclaren车间手册线路图:语言:English,German,French,Spanish,Chinese,Japanese.McLaren迈凯伦新GT维修手册电路图零配件 ...

  9. Javadoc命令与API

    Javadoc命令与API Javadoc工具会抽取类,成员,方法的文档注释生成与这个类配套的API帮助文档 Javadoc命令和文档注释小公司基本不用,但我们应养成良好的编码习惯,所以还是了解一下 ...

  10. FTP使用Socket SSL流程认证(一)

    关于Ftp使用SSL流程认证 本文章使用的是C#,ftp服务器为FileZilla 注:如果不是使用的Socket可以使用FtpWebRequst类,说实话,该类比较简单,但现在说的是SOCKET,网 ...