poj_3580 伸展树
自己伸展树做的第一个题 poj 3580 supermemo.
题目大意
对一个数组进行维护,包含如下几个操作:
- ADD x, y, d 在 A[x]--A[y] 中的每个数都增加d
- REVERSE x, y 将 A[x]--A[y] 中的数进行反转,变为 A[y],A[y-1]....A[x+1],A[x]
- REVOLVE x, y, T 将 A[x]--A[y]中的数连续右移T次
- INSERT x, P 在A后添加数P
- DELETE x 删除A[x]
- MIN x, y 查询A[x]--A[y]中的最小值
思路
对有序进行操作可选的数据结构有 线段树、treap、伸展树Splay等,这道题要求比较多,所以选用伸展树:
伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作。此时进行某些操作(如 ADD, REVERSE 等),只需要在根节点上做个标记,进行延迟处理(即在之后真正访问子节点时候才对子节点进行实际的更新操作),这样可以节省时间。
每次对树的节点进行修改(比如DELETE, INSERT等)之后,都要进行维护信息,此时需要Update一下,然后将该节点旋转至树根。
且在寻找一个区间的起始点对应在树中的节点的时候,都要将该节点所需要的所有信息带给该节点,这就要求在从根节点向下寻找该节点的时候,将路径上的所有节点(即该节点的祖先节点)上的标记都往下传,即PushDown。
实现(c++)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#define MIN(a,b) a<b? a:b
#define MAX_NODE_NUM 200005
#define INFINITE 1 << 30 struct TreeNode{
int data;
int parent;
int child[2];
int child_dir; //程序相关的信息
bool reverse;
int min;
int lazy;
int size;
//节点的索引为0,表示该节点为无效节点。若节点的parent = 0, 表示该节点为根节点,若节点的子节点为0,表示没有相应的子节点
TreeNode(int d = INFINITE) :
data(d), parent(0), child_dir(0), reverse(false), min(INFINITE), lazy(0), size(1){
child[0] = child[1] = 0;
}
void Reset(){
parent = 0;
child[0] = child[1] = 0;
reverse = false;
size = 1;
lazy = 0;
min = INFINITE;
}
}; 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].min = gTreeNode[node].data;
gTreeNode[node].size = 1;
int left = gTreeNode[node].child[0], right = gTreeNode[node].child[1];
if (left){
gTreeNode[node].min = MIN(gTreeNode[node].min, gTreeNode[left].min);
gTreeNode[node].size += gTreeNode[left].size;
}
if (right){
gTreeNode[node].min = MIN(gTreeNode[node].min, gTreeNode[right].min);
gTreeNode[node].size += gTreeNode[right].size;
}
} //向下更新信息
void PushDown(int node){
int left = gTreeNode[node].child[0];
int right = gTreeNode[node].child[1];
if (gTreeNode[node].reverse){
LinkNode(node, left, 1);
LinkNode(node, right, 0);
gTreeNode[left].reverse ^= true;
gTreeNode[right].reverse ^= true;
gTreeNode[node].reverse = false;
}
int tmp_add = gTreeNode[node].lazy;
if (tmp_add){
gTreeNode[node].data += tmp_add; gTreeNode[left].min += tmp_add;
gTreeNode[left].lazy += tmp_add;
gTreeNode[right].min += tmp_add;
gTreeNode[right].lazy += tmp_add; gTreeNode[node].lazy = 0;
}
} int BuildTree(int beg, int end){
if (beg > end){
return 0;
}
if (beg == end){
gTreeNode[gNodeCount].data = gTreeNode[gNodeCount].min = gNumber[beg];
return gNodeCount++;
}
int mid = (beg + end) / 2;
int left = BuildTree(beg, mid - 1);
int right = BuildTree(mid + 1, end); gTreeNode[gNodeCount].data = gTreeNode[gNodeCount].min = gNumber[mid];
LinkNode(gNodeCount, left, 0);
LinkNode(gNodeCount, right, 1);
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 = 0;
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[0];
tmp_size = gTreeNode[left].size;
if (!left){//left 为空节点
if (k == 1){
return node;
}
else{
node = gTreeNode[node].child[1];
k--;
continue;
}
}
if (tmp_size + 1 == k){
return node;
}
else if (tmp_size >= k){
node = left;
}
else{
node = gTreeNode[node].child[1];
k -= (tmp_size + 1);
}
}
return -1;
} //选择区间,返回由该区间构成的子树的节点。节点为 根节点的右子节点的左子节点
int SelectInterval(int x, int y){
if (x <= 0 || y > gNodeCount){
printf("fuck this splay tree!!!\n");
return -1;
}
if (x == 1 && y == gNodeCount - 1){
return gRootIndex;
}
int node;
if (x == 1){
node = GetKthInTree(y + 1);
Splay(node, 0);
return gTreeNode[node].child[0];
}
if (y == gNodeCount - 1){
node = GetKthInTree(x - 1);
Splay(node, 0);
return gTreeNode[node].child[1];
}
int node_beg = GetKthInTree(x - 1);
Splay(node_beg, 0);
int node_end = GetKthInTree(y + 1);
Splay(node_end, gRootIndex); return gTreeNode[node_end].child[0];
}
void Add(int x, int y, int d){
int node = SelectInterval(x, y);
gTreeNode[node].min += d;
gTreeNode[node].lazy += d;
Splay(node, 0);
} void Reverse(int x, int y){
int node = SelectInterval(x, y);
gTreeNode[node].reverse ^= true; //注意是 ^= 而不是 直接 = (因为两次反转相当于不进行反转)
Splay(node, 0);
} void Revolve(int x, int y, int k){
int w = y - x + 1;
k = (k % w + w) % w;
if (k == 0){
return;
}
int node = SelectInterval(x, y);
PushDown(node); int p = gTreeNode[node].parent;
int node_x = GetKthInTree(x);
Splay(node_x, p);
PushDown(node_x); int node_y_sub_k = GetKthInTree(y - k);
Splay(node_y_sub_k, node_x);
PushDown(node_y_sub_k); int node_tmp = gTreeNode[node_y_sub_k].child[1];
LinkNode(node_x, node_tmp, 0);
gTreeNode[node_y_sub_k].child[1] = 0; //注意,node_y_sub_k 发生了改变,因此要更新
Update(node_y_sub_k); Splay(node_tmp, 0);
} void Insert(int x, int t){
int node = SelectInterval(x, x);
gTreeNode[gNodeCount].data = t;
//将节点的信息push down,否则,如果该节点的信息没有被清除,在插入新节点后,可能会对新节点产生影响(因为新节点在该节点下方)
PushDown(node); LinkNode(node, gNodeCount, 1); Splay(gNodeCount, 0);
gNodeCount++;
}
void Delete(int x){
int node = SelectInterval(x, x);
PushDown(gTreeNode[node].parent); gNodeCount--;
int p = gTreeNode[node].parent;
gTreeNode[p].child[gTreeNode[node].child_dir] = 0; //去掉父节点的子节点
//这里更改了,node,会导致node的父节点的信息发生改变,因此要进行维护!!!!
Update(p);
Splay(p, 0); p = gTreeNode[gNodeCount].parent;
int left = gTreeNode[gNodeCount].child[0];
int right = gTreeNode[gNodeCount].child[1]; if (node == gNodeCount){
gTreeNode[gNodeCount].Reset();
return;
} gTreeNode[node] = gTreeNode[gNodeCount];
LinkNode(p, node, gTreeNode[gNodeCount].child_dir);
if (p == 0){
gRootIndex = node; //可能会对根部造成改变
}
LinkNode(node, left, 0);
LinkNode(node, right, 1); gTreeNode[gNodeCount].Reset();
} int GetMin(int x, int y){
int node = SelectInterval(x, y);
//获得节点之后,一定要进行更新!!!
PushDown(node);
Update(node);
return gTreeNode[node].min;
} void debug(int node){
if (node){
debug(gTreeNode[node].child[0]); printf("node %d, parent = %d, left = %d, right = %d, data = %d, min = %d, lazy = %d, reverse = %d\n",
node, gTreeNode[node].parent, gTreeNode[node].child[0], gTreeNode[node].child[1],
gTreeNode[node].data, gTreeNode[node].min, gTreeNode[node].lazy, gTreeNode[node].reverse); debug(gTreeNode[node].child[1]);
}
}
int main(){
int node_num;
gNodeCount = 1;
scanf("%d", &node_num);
for (int i = 0; i < node_num; i++){
scanf("%d", gNumber + i);
} gRootIndex = BuildTree(0, node_num - 1); //递归的方式构造一棵开始就平衡的二叉树
gTreeNode[gRootIndex].parent = 0; //根 int query_num;
scanf("%d", &query_num);
char op[10];
int x, y, tmp; for (int i = 0; i < query_num; i++){
// debug(gRootIndex); scanf("%s", op); if (strcmp(op, "ADD") == 0){
scanf("%d%d%d", &x, &y, &tmp);
Add(x, y, tmp);
}
else if (strcmp(op, "REVERSE") == 0){
scanf("%d%d", &x, &y);
Reverse(x, y);
}
else if (strcmp(op, "REVOLVE") == 0){
scanf("%d%d%d", &x, &y, &tmp);
Revolve(x, y, tmp);
}
else if (strcmp(op, "INSERT") == 0){
scanf("%d%d", &x, &tmp);
Insert(x, tmp);
}
else if (strcmp(op, "DELETE") == 0){
scanf("%d", &x);
Delete(x);
}
else if (strcmp(op, "MIN") == 0){
scanf("%d%d", &x, &y);
printf("%d\n", GetMin(x, y));
}
/*
for (int i = 1; i < gNodeCount; i ++){
printf("%d ", GetMin(i, i));
}
printf("\n\n");
*/
}
return 0;
}
poj_3580 伸展树的更多相关文章
- 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更新== ...
随机推荐
- 【C#/WPF】图像变换的Undo撤销——用Stack命令栈
需求: 图层中有一张图片,可以对该图层进行平移.缩放.旋转操作,现在要求做Undo撤销功能,使得图层回复上一步操作时的状态. 关于图像的平移.缩放.旋转,可以参考在下的另一篇博客的整理: http:/ ...
- C语言 · 分糖果
历届试题 分糖果 时间限制:1.0s 内存限制:256.0MB 问题描述 有n个小朋友围坐成一圈.老师给每个小朋友随机发偶数个糖果,然后进行下面的游戏: 每个小朋友都把自己的糖果分一 ...
- AM335X can驱动移植
驱动选择 make menuconfig [*] Networking support ---> <*> CAN bus subsystem support ---> --- ...
- android矩阵具体解释
Matrix.中文里叫矩阵,高等数学里有介绍,在图像处理方面,主要是用于平面的缩放.平移.旋转等操作. 在Android里面,Matrix由9个float值构成.是一个3*3的矩阵. 最好记住.例如以 ...
- 最大似然估计 (MLE)与 最大后验概率(MAP)在机器学习中的应用
最大似然估计 MLE 给定一堆数据,假如我们知道它是从某一种分布中随机取出来的,可是我们并不知道这个分布具体的参,即“模型已定,参数未知”. 例如,对于线性回归,我们假定样本是服从正态分布,但是不知道 ...
- python中的argsort函数
>>> import numpy >>> help(numpy.argsort) Help on function argsort in module numpy. ...
- 弄明白html、css3、js这个问题。。。
- 类加载器详解 (转至http://blog.csdn.net/jiangwei0910410003/article/details/17733153)
首先来了解一下字节码和class文件的区别: 我们知道,新建一个java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的 ...
- ubuntu环境JDK安装(转至 http://hi.baidu.com/leo_lovato/item/31d1150d31a06d8002ce1bec)
ubuntu安装jdk 1.首先去官网http://www.oracle.com/technetwork/java/javase/downloads/index.html 下载最新版的jdk.我下载了 ...
- 扒一扒MathType不为人知的技巧
MathType作为一款编辑数学公式的神器,很多人在使用它时只是很简单地使用了一些最基本的模板,很多功能都没有使用.MathType功能比你想象中的大很多,今天我们就来扒一扒MathType那些不为人 ...