动态主席树【带修改】&& 例题 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 ...
随机推荐
- Python基础语法2-数据类型
一,数字. 2. 字符串类型 3.列表 4.元组 5.集合 6.字典 7.数据类型转换: 8.序列操作
- 【Docker】Docker概述、理解docker的集装箱、标准化、隔离的思想、 docker出现解决了什么问题
整理一下 慕课网 第一个docker化的java应用 Docker环境下的前后端分离项目部署与运维 课程时所做的笔记 Docker概述 docker - https://www.docker.com/ ...
- 【ORA】ORA-16629解决办法
数据库向保护模式报告不同的保护级别"警告消息. 首先查看主备库的保护模式和保护级别 select protection_mode,protection_level from v$databa ...
- oracle 12C单实例打PSU
前提: oracle不管打什么样的补丁,readme都是很好的参考资料. Oracle每季度都会更新一个最新的PSU,现在12.1.0.2.0的最新的PSU是Patch 26925311. 由于今天白 ...
- uni-app开发经验分享一: 多页面传值的三种解决方法
开发了一年的uni-app,在这里总结一些uni-app开发中的问题,提供几个解决方法,分享给大家: 问题描述:一个主页面,需要联通一到两个子页面,子页面传值到主页面,主页面更新 问题难点: 首先我们 ...
- Django-http协议
Http协议:超文本传输协议(应用层程序).它是客户端和服务端请求和应答的标准.Http的请求响应模型:1.客户端连接到web服务器一个http客户端,与web服务器的http端口(默认是80)建立了 ...
- 使用Azure Runbook 发送消息到Azure Storage Queue
客户需要定时发送信息到Azure Storage Queue,所以尝试使用Azure Runbook实现这个需求. 首先新增一个Azure Automation Account的资源. 因为要使用Az ...
- Go for Pythonistas Go and the Zen of Python 禅
Go for Pythonistas https://talks.golang.org/2013/go4python.slide#1 Things I don't like about Python ...
- 消息中间件——rocketmq环境配置
产生原因 RocketMQ概述 RocketMQ 是一款分布式.队列模型的消息中间件,具有以下特点: 能够保证严格的消息顺序 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅机制 亿级 ...
- XV6学习(8)中断和设备驱动
驱动是操作系统中用于管理特定设备的代码:驱动控制设备硬件,通知硬件执行操作,处理中断,与等待该设备IO的进程进行交互. 当设备需要与操作系统进行交互时,就会产生中断(陷阱的一种),之后内核的陷阱处理代 ...