当前位置: 首页 > 编程日记 > 正文

【bzoj1251】序列终结者(伸展树)

【bzoj1251】序列终结者(伸展树)

Description

网上有许多题,就是给定一个序列,要你支持几种操作:A、B、C、D。一看另一道题,又是一个序列 要支持几种操作:D、C、B、A。尤其是我们这里的某人,出模拟试题,居然还出了一道这样的,真是没技术含量……这样 我也出一道题,我出这一道的目的是为了让大家以后做这种题目有一个“库”可以依靠,没有什么其他的意思。这道题目 就叫序列终结者吧。 【问题描述】 给定一个长度为N的序列,每个序列的元素是一个整数(废话)。要支持以下三种操作: 1. 将[L,R]这个区间内的所有数加上V。 2. 将[L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1。 3. 求[L,R]这个区间中的最大值。 最开始所有元素都是0。

Input

第一行两个整数N,M。M为操作个数。 以下M行,每行最多四个整数,依次为K,L,R,V。K表示是第几种操作,如果不是第1种操作则K后面只有两个数。

Output

对于每个第3种操作,给出正确的回答。

Sample Input

4 4
1 1 3 2
1 2 4 -1
2 1 3
3 2 4

Sample Output

2
【数据范围】
N<=50000,M<=100000。

样例说明:

0000
1 1 3 22220
1 2 4 -1211-1
2 1 3112-1
3 2 42

分析:

暴力的话,操作1增加操作,操作2翻转操作,操作3查询操作的复杂度都是O(n),并且有m个查询的话,O(mn)肯定得爆炸。

关键点:
1. 伸展树为左小右大的二叉树,所以旋转操作不会影响树的性质
2. 区间操作为:
int u = select(L - 1), v = select(R + 1);
splay(u, 0); splay(v, u); //通过旋转操作把询问的区间聚集到根的右子树的左子树下
因为伸展树为左小右大的二叉树,旋转操作后的所以对于闭区间[L, R]之间的所有元素都聚集在根的右子树的左子树下
因为闭区间[L, R],
1) 所以每次都要查开区间(L - 1, R + 1),
2) 所以伸展树元素1对应的标号为2,
3) 所以node[0]对应空节点,node[1]对应比所以元素标号都小的点,node[2 ~ n + 1]对应元素1 ~ n,node[n + 2]对应比所有元素标号都打的点,其中node[0], node[1], node[n + 2]都是虚节点,不代表任何元素。

每次进行序列操作时,把l-1旋转到根,把r+1旋转到根的右儿子,r+1的左子树就是整个区间[l,r]

我们可以用Splay的每个节点记录该节点对应子树的信息,那么每次询问只要输出r+1的左子树中的最大值,即代码中的mx[t[y][0]]。

为了避免Splay中有节点0,我们将所有节点的编号加1。又因为要旋转l-1和r+1,所以在Splay插入节点为1到n+2。(原因显然…大家自己脑补)

这道题用Splay的提根操作达到了区间操作的目的,方法很巧妙。

另外我觉得这道题有几点需要注意:

①要理解Splay中节点的含义以及节点所记录的信息。

②区间的翻转操作很巧妙,只需要将标记下传并且交换左右子树,并不需要修改节点的max和size。

③每次find操作都要pushdown,这样就可以保证节点x到根的路径上所有点都被更新,便于之后的旋转操作

总结

a. 这里区间加,所以无怪乎有延迟标记的思想,那就自然有了pushdown操作

b. 这里通过提根操作找到我们要操作的区间,

c. 区间加操作用的是延迟标记的思想

b. 区间最大值操作在排序二叉树中很简单(因为这个区间已经被我们旋转成一个区间了)

d. 区间翻转操作:这里用的是延迟标记的思想(区间加也是延迟标记),所以有rev做延迟标记,交换的话直接交换左右即可

e. 这里有翻转操作,这颗伸展树不一定是一颗二叉排序树,所以求最大值的话就像线段树那么求好了,每个节点多加个maxx标记即可

  1 /*bzoj 1251 序列终结者
  2   题意:
  3   给定一个长度为N的序列,每个序列的元素是一个整数。要支持以下三种操作:
  4   1. 将[L,R]这个区间内的所有数加上V;
  5   2. 将[L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1;
  6   3. 求[L,R]这个区间中的最大值;
  7   最开始所有元素都是0。
  8   限制:
  9   N <= 50000, M <= 100000
 10   思路:
 11   伸展树
 12 
 13   关键点:
 14   1. 伸展树为左小右大的二叉树,所以旋转操作不会影响树的性质
 15   2. 区间操作为:
 16         int u = select(L - 1), v = select(R + 1);
 17         splay(u, 0); splay(v, u);    //通过旋转操作把询问的区间聚集到根的右子树的左子树下
 18      因为伸展树为左小右大的二叉树,旋转操作后的所以对于闭区间[L, R]之间的所有元素都聚集在根的右子树的左子树下
 19      因为闭区间[L, R],
 20      1) 所以每次都要查开区间(L - 1, R + 1),
 21      2) 所以伸展树元素1对应的标号为2,
 22      3) 所以node[0]对应空节点,node[1]对应比所以元素标号都小的点,node[2 ~ n + 1]对应元素1 ~ n,node[n + 2]对应比所有元素标号都打的点,其中node[0], node[1], node[n + 2]都是虚节点,不代表任何元素。
 23  */
 24 #include <iostream>
 25 #include <cstdio>
 26 using namespace std;
 27 //左右孩子简便写 
 28 #define LS(n) node[(n)].ch[0]
 29 #define RS(n) node[(n)].ch[1]
 30 
 31 const int N = 1e5 + 5;
 32 const int INF = 0x3f3f3f3f;
 33 struct Splay {
 34     struct Node{
 35         int fa, ch[2];//节点的父亲以及两个孩子 
 36         bool rev;//翻转标记 
 37         int val, add, maxx, size;//值,增加的延迟标记,最大值,子树的大小 
 38         void init(int _val) {
 39             val = maxx = _val;//初始化最大值和值 
 40             size = 1;//子树大小 
 41             add = rev = ch[0] = ch[1] = 0;//初始化左右子树和延迟标记和反转标记 
 42         }
 43     } node[N];//n个节点 
 44     int root;//树根 
 45 
 46     void up(int n) {//右节点向父亲更新 
 47         //这是求子树的最大值,树的最大值就是取树根,左子树,右子树三者中的最大值 
 48         node[n].maxx = max(node[n].val, max(node[LS(n)].maxx, node[RS(n)].maxx));
 49         //这是更新树根的大小,左子树+右子树+根 
 50         node[n].size = node[LS(n)].size + node[RS(n)].size + 1;
 51     }
 52 
 53     void down(int n) {//区间增加的延迟标记往下传的操作 
 54         if(n == 0) return ;//空节点 
 55         if(node[n].add) {//如果增加的延迟标记不为0 
 56             if(LS(n)) {//如果分别有左右子树,就更新左右子树
 57                 //标准的线段树区间操作的例子 
 58                 node[LS(n)].val += node[n].add;
 59                 node[LS(n)].maxx += node[n].add;
 60                 node[LS(n)].add += node[n].add;
 61             }
 62             if(RS(n)) {
 63                 node[RS(n)].val += node[n].add;
 64                 node[RS(n)].maxx += node[n].add;
 65                 node[RS(n)].add += node[n].add;
 66             }
 67             node[n].add = 0;//增加延迟标记传下去了,自己的当然要赋值为0 
 68         }
 69         if(node[n].rev) {//这是区间翻转的延迟标记 
 70             if(LS(n)) node[LS(n)].rev ^= 1;//翻转延迟标记往下传 
 71             if(RS(n)) node[RS(n)].rev ^= 1;
 72             swap(LS(n), RS(n));//交换左右子树 
 73             node[n].rev = 0;//翻转延迟标记设置为0 
 74         }
 75     }
 76 
 77     //左旋和右旋的合集 ,将节点n按照kind方式旋转 
 78     void rotate(int n, bool kind) {
 79         int fn = node[n].fa;
 80         int ffn = node[fn].fa;
 81         node[fn].ch[!kind] = node[n].ch[kind];
 82         node[node[n].ch[kind]].fa = fn;
 83         
 84         node[n].ch[kind] = fn;
 85         node[fn].fa = n;
 86 
 87         node[ffn].ch[RS(ffn) == fn] = n;
 88         node[n].fa = ffn;
 89         up(fn);
 90     }
 91 
 92     //将节点n伸展到goal的 位置去 
 93     void splay(int n, int goal) {
 94         while(node[n].fa != goal) {
 95             int fn = node[n].fa;
 96             int ffn = node[fn].fa;
 97             down(ffn); down(fn); down(n);
 98             bool rotate_n = (LS(fn) == n);
 99             bool rotate_fn = (LS(ffn) == fn);
100             if(ffn == goal) rotate(n, rotate_n);
101             else {
102                 if(rotate_n == rotate_fn) rotate(fn, rotate_fn);
103                 else rotate(n, rotate_n);
104                 rotate(n, rotate_fn);
105             }
106         }
107         up(n);
108         if(goal == 0) root = n;
109     }
110 
111     //在树种找位置为pos的点,其实和二叉查找树里面找排名为pos的点的方式一样 
112     int select(int pos) {
113         int u = root;
114         down(u);//区间加和区级翻转延迟标记下传 
115         while(node[LS(u)].size != pos) {//左孩子的大小不等于pos 
116             if(pos < node[LS(u)].size)//如果pos在左孩子就往左孩子走 
117                 u = LS(u);//
118             else {//pos在右孩子 
119                 pos -= node[LS(u)].size + 1;//pos减去左孩子和根的大小 
120                 u = RS(u);//往右孩子走 
121             }
122             down(u);//延迟标记下传 
123         }
124         return u;
125     }
126 
127     //查找l到r这个区间 
128     int query(int L, int R) {
129         //u节点就是找到的l-1的节点,v节点就是找到的r+1的节点 
130         int u = select(L - 1), v = select(R + 1);
131         //将u节点旋转到0的位置(根),将v节点旋转到u的位置,那么 
132         splay(u, 0); splay(v, u);    //通过旋转操作把询问的区间聚集到根的右子树的左子树下
133         return node[LS(v)].maxx;
134     }
135 
136     //区间加操作 
137     void update(int L, int R, int val) {
138         //把区间调上来 
139         int u = select(L - 1), v = select(R + 1);
140         splay(u, 0); splay(v, u);
141         //标准的区间加操作 
142         node[LS(v)].val += val;
143         node[LS(v)].maxx += val;
144         node[LS(v)].add += val;//延迟标记下传 
145     }
146     
147     //区间翻转操作 
148     void reverse(int L, int R) {
149         //找区间 
150         int u = select(L - 1), v = select(R + 1);
151         splay(u, 0); splay(v, u);
152         //翻转延迟标记置为1 
153         node[LS(v)].rev ^= 1;
154     }
155 
156     int build(int L, int R) {
157         if(L > R) return 0;
158         if(L == R) return L;
159         int mid = (L + R) >> 1;
160         int r_L, r_R;
161         LS(mid) = r_L = build(L, mid - 1);
162         RS(mid) = r_R = build(mid + 1, R);
163         node[r_L].fa = node[r_R].fa = mid;
164         up(mid);
165         return mid;
166     }
167 
168     void init(int n) {
169         node[0].init(-INF); node[0].size = 0;
170         node[1].init(-INF);
171         node[n + 2].init(-INF);
172         for(int i = 2; i <= n + 1; ++i)
173             node[i].init(0);
174         
175         root = build(1, n + 2);
176         node[root].fa = 0;
177 
178         node[0].fa = 0;
179         LS(0) = root;
180     }
181 } splay_tree;
182 
183 int main() {
184     int n, m;
185     scanf("%d%d", &n, &m);
186     splay_tree.init(n);//初始化 
187     for(int i = 0; i < m; ++i) {
188         int op, l, r, v;
189         scanf("%d", &op);
190         if(op == 1) {//操作1,区间加 
191             scanf("%d%d%d", &l, &r, &v);
192             splay_tree.update(l, r, v);//l到r区间上面加上v 
193         } else if(op == 2) {//操作2,区间翻转 
194             scanf("%d%d", &l, &r);
195             splay_tree.reverse(l, r);//翻转l到r区间 
196         } else {
197             scanf("%d%d", &l, &r);
198             printf("%d\n",splay_tree.query(l, r));//查询l到r的最大值 
199         }
200     }
201     return 0;
202 }

转载于:https://www.cnblogs.com/Renyi-Fan/p/8141712.html

相关文章:

再谈PowerPoint 2010导出幻灯片为图片

前些日子写了篇《利用VBA导出幻灯片为图片》&#xff0c;结果被Jackson告知&#xff0c;PowerPoint 2010已经有此功能了&#xff0c;并且PowerPoint 2007可能就已经有了。并且经最终验证&#xff0c;在PowerPoint 2003中同样有此功能。由于平时用PowerPoint并不多&#xff0c;所…

【网络编程】非阻塞connect详解

一、为什么使用非阻塞connect TCP连接的建立涉及一个在三路握手过程&#xff0c;阻塞的connect一直等到客户收到自己的SYN的ACK才返回&#xff0c;这需要至少一个RTT时间&#xff0c;RTT时间波动很大从几毫秒到几秒。而且在没有响应时&#xff0c;会等待数秒再次发送&#xff0…

AI,被“横扫记录”反噬?

编辑 | Jane 出品 | AI科技大本营 昨天&#xff0c;香侬科技发表论文《Glyce: Glyph-vectors for Chinese Character Representations》&#xff0c;提出基于中文字形的 NLP 模型——Glyce。香侬科技官方公开的论文解读中写道&#xff1a; Glyce提出了基于中文字形的语义表示&…

android 入门之一【开发环境搭建】

这里的开发环境采用Eclipseandroid 开发插件&#xff0c;其它的开发环境不做介绍 一.安装JDK android 开发语言是基于Java的&#xff0c;所以要做android的开发必须要安装JDK&#xff0c;并且对JDK的版本有一定的要求必须是JDK5 以上的版本&#xff0c;JDK5以前的版本android不…

一块GPU就能训练语义分割网络,百度PaddlePaddle是如何优化的?

【引言】显存不足是训练语义分割网络常常遇见的问题&#xff0c;而显存是GPU计算中的稀缺资源。百度深度学习框架PaddlePaddle中的显存优化&#xff0c;不仅可以让研究人员在相同成本的计算设备上训练更大的模型&#xff0c;还可以在消费级别显卡上完成训练。在本篇文章中&…

【音频】Faad源码交叉编译

1、源码下载http://www.audiocoding.com/downloads.html2、解压后&#xff0c;进入目录执行如下命令aclocalautoheaderautomake --add-missingautoconf./configure --hostarm-fsl-linux-gnueabi CCarm-fsl-linux-gnueabi-gcc --prefix/home/faad/installmakemake install

springboot 整合redis 实现KeySpaceNotification 键空间通知

2019独角兽企业重金招聘Python工程师标准>>> 目录结构如下&#xff1a; application.properties配置文件&#xff08;redis的配置&#xff09;&#xff1a; spring.redis.hostlocalhost spring.redis.pool.max-idle300 spring.redis.pool.max-wait3000 spring.redis…

黄聪:穿过主机访问虚拟机中的SQL服务 FOR VMware NAT

一般来说&#xff0c;大家都会在主机或者虚拟机中安装SQLIIS&#xff0c;但假如主机的IIS想利用虚拟机中的SQL服务怎么办呢&#xff1f; 以我的电脑为例子&#xff0c;主机系统&#xff1a;Windows 7 7600 RTM X64&#xff0c;安装IIS 7.5。虚拟机系统&#xff1a;Windows 2003…

【数据库】mysql报错 编码码1130 和错误码1146

1、错误编码1130 问题&#xff1a;1130-Hose‘172.16.12.129’is not allowed to connect to this MySQL server 原因&#xff1a;MySQL服务器没有创建&#xff0c;远程客户的账户信息 解决&#xff1a; 1.1 登录 &#xff1a;mysql -uroot 1.2 切换数据库&#xff1a;mysql>…

一键fxxk,代码修复神器拯救你

作者 | 一一出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;在成为一个合格的开发者之前&#xff0c;大多数人一般都经历过被命令行反复“fuck”蹂躏。当然&#xff0c;改代码改不动了&#xff0c;你的内心也是“无 fuck 可说”&#xff0c;尤其在检查半天之后发现这…

hive2.3.2安装使用

hive的安装简单一些,使用也比较简单,基础hadoop搭建好之后,只要初始化一些目录和数据库就好了 安装需要做几件事: 1.设立一个数据源作为元数据存储的地方,默认是derby内嵌数据库,不过不允许远程连接,所以换成mysql 2.配置java路径和classpath路径 下载地址: http://mirrors.shu…

Google经典面试题解析

作者 | Alex Golec译者 | 弯月责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;在深入问题之前&#xff0c;有一个令人振奋的消息&#xff1a;我离开了Google&#xff01;我激动地宣布&#xff0c;我已经加入了Reddit&#xff0c;并在纽约市担任项目经理…

1分钟构建API网关日志解决方案

访问日志&#xff08;Acccess Log&#xff09;是由web服务生成的日志&#xff0c;每一次api请求都对应一条访问记录&#xff0c;内容包括调用者IP、请求的URL、响应延迟、返回状态码、请求和响应字节数等重要信息。 阿里云API网关提供API托管服务&#xff0c;在微服务聚合、前后…

ISQL*PLUS

1、有以下几种命令&#xff1a;环境&#xff1a;影响会话期间SQL语句的总体行为&#xff1b;格式化&#xff1a;格式化查询结果&#xff1b;文件处理&#xff1a;保存语句到脚本文件中&#xff0c;从脚本文件中运行语句&#xff1b;执行&#xff1a;从浏览器发送SQL语句到oracl…

【数据库】mysql 常用命令(一)

1、启动、停止mysql服务 1.0 sudo service mysql restart //测试有效 以下未测试 1.1 使用mysqld mysqld start mysqld stop 1.2 使用mysqld_safe启动、关闭MySQL服务 mysqld_safe 1.3 使用mysql.server启动、关闭MySQL服务 mysql.server stop …

15 个 JavaScript Web UI 库

新闻来源:speckboy.com几乎所有的富 Web 应用都基于一个或多个 Web UI 库或框架&#xff0c;这些 UI 库与框架极大地简化了开发进程&#xff0c;并带来一致&#xff0c;可靠&#xff0c;以及高度交互性的用户界面。本文介绍了 15 个非常强大的 JavaScript Web UI 库&#xff0c…

【网络编程】MarioTCP

0、参考博客 《MarioTCP_一个可单机支持千万并发连接的TCP服务器 - JohanFong - CSDN博客》 http://blog.csdn.net/everlastinging/article/details/10894493 1、下载 sourceforge下载&#xff1a;https://sourceforge.net/projects/mariotcp/files/latest/download 2、安装…

Spring MVC-ContextLoaderListener和DispatcherServlet

2019独角兽企业重金招聘Python工程师标准>>> Spring MVC-ContextLoaderListener和DispatcherServlet 博客分类&#xff1a; spring java Tomcat或Jetty作为Servlet容器会为每一个Web应用构建一个ServletContext用于存放所有的Servlet, Filter, Listener。Spring MVC…

《中国人工智能ABC人才发展报告》发布,算法和应用类人才短缺

近日&#xff0c;百度云联手中国传媒大学、BOSS 直聘和百度指数发布了《中国人工智能 ABC 人才发展报告&#xff08;2018版&#xff09;》&#xff08;以下简称“报告”&#xff09;和百度云智学院2019 年人才认证体系。报告指出&#xff0c;从 2018 年的人才供需状况来看&…

博客域名改为http://bobli.cnblogs.com

本博客的域名已修改为&#xff1a;http://bobli.cnblogs.com/ 原来的地址还可以进入&#xff0c;希望搜索引擎快点更新过来。。。 感谢博客园管理员的帮助&#xff0c;效率非常之高&#xff01;

百度Apollo 3.5是如何设计Cyber RT计算框架的?

自百度Apollo自动驾驶平台开源以来&#xff0c;已快速迭代至 3.5 版本&#xff0c;代码行数超过 39 万行&#xff0c;合作伙伴超过 130 家&#xff0c;吸引了来自 97 个国家的超 15000 名开发者。无疑&#xff0c;Apollo 是目前世界范围内最活跃的自动驾驶开放平台之一。最新发…

Spark Streaming实践和优化

2019独角兽企业重金招聘Python工程师标准>>> Spark Streaming实践和优化 博客分类&#xff1a; spark 在流式计算领域&#xff0c;Spark Streaming和Storm时下应用最广泛的两个计算引擎。其中&#xff0c;Spark Streaming是Spark生态系统中的重要组成部分&#xff0…

Python | 一万多条拼车数据,看春运的迁徙图

作者 | 白苏&#xff0c;医疗健康领域产品经理一枚&#xff0c;Python&R爱好者来源 | InThirty编辑 | Jane今天是腊月二十八&#xff0c;你们都到家了吗&#xff1f;这篇文章&#xff0c;作者对北京、上海、广州、深圳、杭州等地 1万多条出行数据进行分析&#xff0c;得出了…

[转载] sql server 2000系统表解释

sql server 2000系统表解释汇总了几个比较有用的系统表&#xff0c;内容摘自联机帮助sysobjects---------------在数据库内创建的每个对象&#xff08;约束、默认值、日志、规则、存储过程等&#xff09;在表中占一行。只有在 tempdb 内&#xff0c;每个临时对象才在该表中占一…

【驱动】uboot环境变量分析

0、bootcmd 0.1 飞凌原设置 bootcmdif mmc rescan; then if run loadbootscript; then run bootscript; else if test ${bootdev} sd1; then echo update firmware.........;run update_from_sd;else echo mmc boot..........;if run loadimage; then run mmcboot; else run n…

python--属性魔法方法

转载于:https://www.cnblogs.com/Purp1e/p/8149773.html

利用三层交换机实现VLAN的通信实验报告

利用三层交换机实现VLAN的通信实验报告<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />背景&#xff1a;要想实现VLAN之间的通讯,我们可以采用通过路由器实现VLAN间的通信 使用路由器实现VLAN间通信时&#xff0c;路由器与交换机…

【Qt】Qt Creator中文输入设置

#【Qt】Qt Creator中文输入设置 一、ubuntu中文输入法的设置 1、在终端中输入&#xff1a; $ ibus-setup 弹出界面如图&#xff1a; 2、选择中文输入法 3、点击右上角设置–》选择系统设置–》选择语言支持 4、语言支持选择&#xff1a; 汉语&#xff08;中国&#xff09…

为何Google将几十亿行源代码放在一个仓库?

作者 | Rachel Potvin&#xff0c;Josh Levenberg 译者 | 张建军 编辑 | apddd 【AI科技大本营导读】与大多数开发者的想象不同&#xff0c;Google只有一个代码仓库——全公司使用不同语言编写的超过10亿文件&#xff0c;近百TB源代码都存放在自行开发的版本管理系统Piper中&…