参考链接: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. 【Linux】ABRT has detected 1 problem(s). For more info run: abrt-cli list --since 1548988705

    ------------------------------------------------------------------------------------------------- | ...

  2. ctfshow—web—web3

    打开靶机 提示是文件包含漏洞 测试成功 https://d7c9f3d7-64d2-4110-a14b-74c61f65893c.chall.ctf.show/?url=../../../../../ ...

  3. vxfs(Veritas File System)扩充目录大小

    1.新增加一个磁盘并初始化 # vxdisk list # vxdisksetup -i 3pardata0_22 2.将新增加的磁盘合并到磁盘组中 # vxdg -g testdg01 adddis ...

  4. Nginx基础环境搭建

    1.下载docker toolbox https://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/ 2.选择好安装目录 一路nex ...

  5. USB过压保护芯片,高输入电压充电器(OVP)

    PW2606B是一种前端过电压和过电流保护装置.它实现了广泛的输入电压范围从2.5VDC到40VDC.过电压阈值可在外部或外部编程设置为内部默认设置.集成功率路径nFET开关的低电阻确保了更好的性能电 ...

  6. 网络流量预测入门(一)之RNN 介绍

    目录 网络流量预测入门(一)之RNN 介绍 RNN简介 RNN 结构 RNN原理 结构原理 损失函数$E$ 反向传播 总结 参考 网络流量预测入门(一)之RNN 介绍 了解RNN之前,神经网络的知识是 ...

  7. CoeMonkey少儿编程第4章 变量

    点击这里,现在就开启CodeMonkey的趣味编程之旅. 目标 了解什么是变量 了解变量的命名规则 掌握如何使用变量 变量 什么是变量?顾名思义,变量就是可以变化的量. 和变量相对的是常量,即不可变化 ...

  8. codevs3639

    题目描述 Description 给出一棵树,求出树的中心. 为了定义树的中心,首先给每个结点进行标号.对于一个结点K,如果把K从树中删除(连同与它相连的边一起),剩下的被分成了很多块,每一块显然又是 ...

  9. 在 WebAssembly 中实现回调的方式

    本文将介绍在 C++ 中实现 js 回调的几种方式. 在使用 wasm 的过程中, 避免不了要从 C++ 回调 js 的函数来实现异步交互. 官网文档 https://emscripten.org/d ...

  10. Folly解读(零) Fbstring—— 一个完美替代std::string的库

    string 常见的三种实现方式 eager copy COW SSO Fbstring 介绍 Storage strategies Implementation highlights Benchma ...