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更新== ...
随机推荐
- jsonp爬取页面
jsonp Connection conn = Jsoup.connect("http://ip.taobao.com/service/getIpInfo.php?ip=" + i ...
- 【WPF/C#】使用BackgroundWorker实现多线程/异步操作
做WPF时需要做一个异步加载时的Loading遮罩,搜Stackoverflow看到很多方法,看到了这个插件: BusyIndicator in the extended WPF Toolkit 同时 ...
- datagrid中reoload提交时如何批量提交表单中的查询条件
看标题描述有点复杂,看下图: 直接将手工添加的一个个字段直接用一句代码完成. $('#dg_sub').datagrid("reload",$('#searchForm').ser ...
- Oracle分页(limit方式的运用)
select * from a_matrix_navigation_map where rowid not in(select rowid from a_matrix_navigation_map w ...
- phalcon的CLI应用
CLI应用是命令行下执行的程序, 可以应用于定时任务,守护进程, 脚本, 公用命令等等. 最小的目录结构:app/config/config.phpapp/tasks/MainTask.phpapp/ ...
- json数据 提示框flash.now[:notice] flash.now[:alert]
实现json.做出提示框 1.在controller中使用flash.now[:alert] = "str"方法来做print def topodata #@vnic = Vnic ...
- webstorm软件使用记录
右边的那条线的去除:setting-editor-appearance-show right margin 勾选去掉
- 关于Cocos2d-x中父子节点的互动
1.子节点可以通过this->getParent()来获得相应的父节点,并且进行强制类型转换. ((Scene*)this->getParent())->getPhysicsWorl ...
- 关于Cocos2d-x数据类型的使用
常用的是三种数据类型,Value,Vector,Map,翻译成中文就是值,数组,字典.其中字典的意思就是拿着某个关键字去这个数据结构里面找相应的对应的数据. //Value数据类型 Value int ...
- 50个必备的实用jQuery代码段(转)
1. 如何创建嵌套的过滤器: //允许你减少集合中的匹配元素的过滤器, //只剩下那些与给定的选择器匹配的部分.在这种情况下, //查询删除了任何没(:not)有(:has) //包含class为“s ...