poj_3468 伸展树
题目大意
一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和。(这里区间指的是数列中连续的若干个数)对每次询问给出结果。
思路
1. 伸展树的一般规律
对于区间的查找更新操作,可以考虑使用伸展树、线段树等数据结构。这里使用伸展树来解决。
伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作。此时进行某些操作(如 ADD, SUM 等),只需要在根节点上做个标记,进行延迟处理(即在之后真正访问子节点时候才对子节点进行实际的更新操作),这样可以节省时间。
每次对树的节点进行修改(比如DELETE, INSERT等)之后,都要进行维护信息,此时需要Update一下,然后将该节点旋转至树根。
且在寻找一个区间的起始点对应在树中的节点的时候,都要将该节点所需要的所有信息带给该节点,这就要求在从根节点向下寻找该节点的时候,将路径上的所有节点(即该节点的祖先节点)上的标记都往下传,即PushDown。
2. 针对本题的分析
此题中的节点信息需要维护 data(节点的数据大小)、size(以该节点为根的子树的大小)、lazy(节点需要加的数值)、sum(以该节点为根的子树所代表的区间在此刻的区间和)
在每次找到一个区间,形成一棵子树,对该区间进行加一个数值D的时候,需要在该子树根节点位置的lazy加上值D而不需要下放到每个树中的子节点,同时该节点的sum值需要加上 D*节点的size。
在节点下放的PushDown过程中,进行如下操作:
//向下更新信息
void PushDown(int node){
int left = gTreeNode[node].child[];
int right = gTreeNode[node].child[]; long long tmp_add = gTreeNode[node].lazy;
if (tmp_add){
gTreeNode[node].data += tmp_add; gTreeNode[left].lazy += tmp_add;
gTreeNode[left].sum += (gTreeNode[left].size * tmp_add); gTreeNode[right].lazy += tmp_add;
gTreeNode[right].sum += (gTreeNode[right].size * tmp_add);
gTreeNode[node].lazy = ;
}
}
在节点的Update过程中,需要进行如下操作:
//维护本节点信息
void Update(int node){
gTreeNode[node].sum = gTreeNode[node].data;
gTreeNode[node].size = ;
int left = gTreeNode[node].child[], right = gTreeNode[node].child[];
if (left){
gTreeNode[node].size += gTreeNode[left].size;
gTreeNode[node].sum += gTreeNode[left].sum;
}
if (right){
gTreeNode[node].size += gTreeNode[right].size;
gTreeNode[node].sum += gTreeNode[right].sum;
}
//注意,此时的sum值属于以该节点为根的子树,因此每次都需要从 gTreeNode[node].data 开始加起,而不应该保存原来的值。(这个好像不直观,但画图是可以证明在Splay中,这个sum值是可以保证始终为以该节点为根的子树区间和的)
}
以及需要注意数据的范围, data、lazy和sum均需要为 64位整数。
实现(c++)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#define MIN(a,b) a<b? a:b
#define MAX_NODE_NUM 100005
#define INFINITE 1 << 30 struct TreeNode{
long long data;
int parent;
int child[];
int child_dir; //程序相关的信息
long long sum;
long long lazy;
int size;
//节点的索引为0,表示该节点为无效节点。若节点的parent = 0, 表示该节点为根节点,若节点的子节点为0,表示没有相应的子节点
TreeNode(int d = INFINITE) :
data(d), parent(), child_dir(), sum(), lazy(), size(){
child[] = child[] = ;
}
void Reset(){
parent = ;
child[] = child[] = ;
size = ;
lazy = ;
sum = ;
}
}; TreeNode gTreeNode[MAX_NODE_NUM];
int gNumber[MAX_NODE_NUM];
int gNodeCount;
int gRootIndex; void LinkNode(int par, int ch, int dir){
gTreeNode[par].child[dir] = ch;
gTreeNode[ch].parent = par;
gTreeNode[ch].child_dir = dir;
}
//维护本节点信息
void Update(int node){
gTreeNode[node].sum = gTreeNode[node].data;
gTreeNode[node].size = ;
int left = gTreeNode[node].child[], right = gTreeNode[node].child[];
if (left){
gTreeNode[node].size += gTreeNode[left].size;
gTreeNode[node].sum += gTreeNode[left].sum;
}
if (right){
gTreeNode[node].size += gTreeNode[right].size;
gTreeNode[node].sum += gTreeNode[right].sum;
}
} //向下更新信息
void PushDown(int node){
int left = gTreeNode[node].child[];
int right = gTreeNode[node].child[]; long long tmp_add = gTreeNode[node].lazy;
if (tmp_add){
gTreeNode[node].data += tmp_add; gTreeNode[left].lazy += tmp_add;
gTreeNode[left].sum += (gTreeNode[left].size * tmp_add); gTreeNode[right].lazy += tmp_add;
gTreeNode[right].sum += (gTreeNode[right].size * tmp_add);
gTreeNode[node].lazy = ;
}
} int BuildTree(int beg, int end){
if (beg > end){
return ;
}
if (beg == end){
gTreeNode[gNodeCount].data = gTreeNode[gNodeCount].sum = gNumber[beg];
return gNodeCount++;
}
int mid = (beg + end) / ;
int left = BuildTree(beg, mid - );
int right = BuildTree(mid + , end); gTreeNode[gNodeCount].data = gTreeNode[gNodeCount].sum = gNumber[mid];
LinkNode(gNodeCount, left, );
LinkNode(gNodeCount, right, );
Update(gNodeCount);
return gNodeCount++;
} //zig or zag旋转
void Rotate(int x){
if (x == gRootIndex){
return;
}
int y = gTreeNode[x].parent;
PushDown(y);
PushDown(x);
int d = gTreeNode[x].child_dir; int z = gTreeNode[y].parent;
LinkNode(z, x, gTreeNode[y].child_dir);
LinkNode(y, gTreeNode[x].child[!d], d);
LinkNode(x, y, !d);
Update(y);
if (y == gRootIndex){
gRootIndex = x;
}
} //旋转操作,将node节点旋转到 f 节点下方
void Splay(int x, int f){
if (x == f){
return;
}
PushDown(x);
int y = gTreeNode[x].parent, z = ;
while (y != f){
z = gTreeNode[y].parent;
if (z == f){
Rotate(x);
break;
}
if (gTreeNode[x].child_dir == gTreeNode[y].child_dir){ //一字型旋转
Rotate(y);
Rotate(x);
}
else{ //之字形旋转
Rotate(x);
Rotate(x);
}
y = gTreeNode[x].parent;
}
Update(x);
}
//获取伸展树中 第k个节点的index
int GetKthInTree(int k){
int node = gRootIndex, left, tmp_size;
while (node){
PushDown(node); //注意要将与该节点有关的信息带下去
left = gTreeNode[node].child[];
tmp_size = gTreeNode[left].size;
if (!left){//left 为空节点
if (k == ){
return node;
}
else{
node = gTreeNode[node].child[];
k--;
continue;
}
}
if (tmp_size + == k){
return node;
}
else if (tmp_size >= k){
node = left;
}
else{
node = gTreeNode[node].child[];
k -= (tmp_size + );
}
}
return -;
} //选择区间,返回由该区间构成的子树的节点。节点为 根节点的右子节点的左子节点
int SelectInterval(int x, int y){
if (x <= || y > gNodeCount){
printf("fuck this splay tree!!!\n");
return -;
}
if (x == && y == gNodeCount - ){
return gRootIndex;
}
int node;
if (x == ){
node = GetKthInTree(y + );
Splay(node, );
return gTreeNode[node].child[];
}
if (y == gNodeCount - ){
node = GetKthInTree(x - );
Splay(node, );
return gTreeNode[node].child[];
}
int node_beg = GetKthInTree(x - );
Splay(node_beg, );
int node_end = GetKthInTree(y + );
Splay(node_end, gRootIndex); return gTreeNode[node_end].child[];
}
void Add(int x, int y, int d){
int node = SelectInterval(x, y);
gTreeNode[node].lazy += d;
gTreeNode[node].sum += gTreeNode[node].size * d;
Splay(node, );
} long long GetSum(int x, int y){
int node = SelectInterval(x, y);
//获得节点之后,一定要进行更新!!!
PushDown(node);
Update(node);
return gTreeNode[node].sum;
} void debug(int node){
if (node){
debug(gTreeNode[node].child[]); printf("node %d, parent = %d, left = %d, right = %d, data = %d, sum = %d, lazy = %d\n",
node, gTreeNode[node].parent, gTreeNode[node].child[], gTreeNode[node].child[],
gTreeNode[node].data, gTreeNode[node].sum, gTreeNode[node].lazy); debug(gTreeNode[node].child[]);
}
}
int main(){
int node_num, query_num;
gNodeCount = ;
scanf("%d%d", &node_num, &query_num);
for (int i = ; i < node_num; i++){
scanf("%d", gNumber + i);
} gRootIndex = BuildTree(, node_num - ); //递归的方式构造一棵开始就平衡的二叉树
gTreeNode[gRootIndex].parent = ; //根 char op[];
int x, y, tmp; for (int i = ; i < query_num; i++){
// debug(gRootIndex); scanf("%s", op);
if (op[] == 'C'){
scanf("%d%d%d", &x, &y, &tmp);
Add(x, y, tmp);
}
else if (op[] == 'Q'){
scanf("%d%d", &x, &y);
printf("%lld\n", GetSum(x, y));
} }
return ;
}
poj_3468 伸展树的更多相关文章
- Splay伸展树学习笔记
Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...
- 纸上谈兵:伸展树(splay tree)
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们讨论过,树的搜索效率与树的深度有关.二叉搜索树的深度可能为n,这种情况下,每次 ...
- SplayTree伸展树的非递归实现(自底向上)
Splay Tree 是二叉查找树的一种,它与平衡二叉树.红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋 ...
- 伸展树(一)之 图文解析 和 C语言的实现
概要 本章介绍伸展树.它和"二叉查找树"和"AVL树"一样,都是特殊的二叉树.在了解了"二叉查找树"和"AVL树"之后, ...
- 伸展树(二)之 C++的实现
概要 上一章介绍了伸展树的基本概念,并通过C语言实现了伸展树.本章是伸展树的C++实现,后续再给出Java版本.还是那句老话,它们的原理都一样,择其一了解即可. 目录1. 伸展树的介绍2. 伸展树的C ...
- 伸展树(三)之 Java的实现
概要 前面分别通过C和C++实现了伸展树,本章给出伸展树的Java版本.基本算法和原理都与前两章一样.1. 伸展树的介绍2. 伸展树的Java实现(完整源码)3. 伸展树的Java测试程序 转载请注明 ...
- hdu1890 伸展树(区间反转)
对于大神来说这题是水题.我搞这题花了快2天. 伸展树的优点有什么,就是树不管你怎么旋转序列是不会改变得,并且你要使区间反转,只要把第k大的点转到根结点,那么它的左子树就是要交换的区间[l,r),然后交 ...
- POJ 3580 (伸展树)
题目链接: http://poj.org/problem?id=3580 题目大意:对一个序列进行以下六种操作.输出MIN操作的结果. 解题思路: 六个操作,完美诠释了伸展树有多么吊.注意,默认使用L ...
- Splay 伸展树
废话不说,有篇论文可供参考:杨思雨:<伸展树的基本操作与应用> Splay的好处可以快速分裂和合并. ===============================14.07.26更新== ...
随机推荐
- [shell]shell脚本统计数值大小
#! /bin/bash array=( ... ) var1= var2= ;i<=;i++)); do array[i]="$( cat /sys/bus/iio/devices/ ...
- FusionCharts JavaScript API - Functions 常用方法整理笔记
FusionCharts JavaScript API - Functions Home > FusionCharts XT and JavaScript > API Reference ...
- BusyBox init工作流程
linux启动完成后,运行由Busybox产生的init进程. /sbin/init是系统启动的第一个用户进程,pid=1.init的工作是根据/etc/inittab脚本来进行系统的初始化工作,关机 ...
- DataTable使用技巧:DataRowState
DataGridView:获取 DataRow 对象的状态,共有5个枚举值. Added 该行已添加到 DataRowCollection 中,AcceptChanges 尚未调用. Deleted ...
- 用 phpize 编译共享 PECL 扩展库
有时候不能用 pecl 安装命令.这可能是因为在防火墙后面,或者是因为想要安装的扩展库还没有 PECL 兼容的包,例如 SVN 中尚未发布的扩展库.如果要编译这种扩展库,可以用更底层的编译工具来手工进 ...
- phpstudy+php5.2+mssql2008
我勒个去.... <?php $server ="XEJMZWMDIXE9CIJ"; //服务器IP地址,如果是本地,可以写成localhost $uid ="&q ...
- selenium测试(Java)--元素操作(五)
元素的操作有 1. 清除文本 2. 模拟按键输入 3. 单击元素 4. 返回元素尺寸 5. 获取文本 6. 获取属性值 7. 判断是否可见 8. 提交 下面通过操作新浪邮箱的注册界面的脚本来展示使用方 ...
- 如何识别Java中的内存泄漏
Java开发人员都知道,Java利用垃圾回收机制来自动保持应用程序内存的干净和健康.然而可能有人不知道的是,即使使用了垃圾回收机制,Java中仍然可能存在内存泄漏风险.如果你碰到下面的错误代码: ja ...
- ERROR 1045 (28000): Access denied for user 'root'@'127.0.0.1' (using password: YES)
我的原因是在配置文件my.ini [mysqld]项,在其后加入了一句:skip-name-resolve 导致授权出现这个错误,把skip-name-resolve这项屏蔽了就好了. 场景2:对所有 ...
- 嵌入式驱动开发之usb 无线网卡驱动---RT2870STA dm368
RT2870STA 368的无线网卡驱动! http://www.linuxidc.com/Linux/2014-02/96979.htm