ACM:平衡树(2)——Splay

题目来源:

HihoCoder1329

题目描述:

定义一个包含数字的集合,集合的初始情况为空。给定下面两种操作:

插入:向集合中添加一个数字k。

询问:询问集合中不超过k的最大数字。

删除:删除落在区间[a, b]内的所有数字。

题目要求对于每次询问,输出对应的答案。

解答:

    本题和HihoCoder1325类似,可以用之前介绍的Treap算法来解答。但Treap树堆有一个问题,节点的权值是随机生成的,因此对树的调整操作也是随机发生的,在某些情况下,Treap树同样也可能会退化为一条线,导致搜索效率降低。另外,本题添加了删除指定区间数据的操作,Treap树中对于删除操作也比较麻烦。

这里介绍另外一种平衡搜索树——Splay。

Splay树的操作:

Splay树和普通的二叉搜索树类似,Splay中文可以译为“伸展树”,它在基础的二叉搜索树的基础上,定义了4种新的操作,定义如下:

zig:

zig操作将树中的某个节点通过旋转提升一层,如下图:

具体来说:如果当前节点是其父节点的左孩子,则右旋,否则,左旋。zig操作后,操作节点提升一层。

zig-zig:

zig-zig操作将某个节点高度提升2层,执行该步骤的前提是:当前节点的父节点,以及当前节点的父节点的父节点均不为空,并且当前节点和其父节点同时为各自父节点的左孩子或右孩子。此时首先对于当前节点的父节点执行zig操作,然后在对当前节点执行zig操作。注意不是对于当前节进行两次zig操作,如下图:

zig-zig操作将节点高度提升2层。

zig-zag:

zig-zag操作和zig-zig操作类似。同样可以将节点高度提升2层。但该操作的前提是:当前节点的父节点,以及当前节点的父节点的父节点均不为空,并且当前节点和其父节点不是同时为各自父节点的左孩子或右孩子。即:当前节点是其父节点的左孩子并且父节点是其父节点的右孩子,或者当前节点是其父节点的右孩子并且父节点是其父节点的左孩子。zig-zag操作对当前节点连续执行两次zig操作将,节点高度提升2层。如下图:

splay:

基于以上的3种操作,我们可以定义Splay树的splay操作。该操作涉及到了两个节点A、B,其中节点A是节点B的祖先节点。通过Splay操作,可以将节点B转化为节点A的子节点。该过程通过对节点B反复进行以上的3中操作,使节点B的高度逐渐提升,直到节点B是节点A的孩子节点。具体步骤如下:

① 判定节点B的父节点是否为节点A。如果是,则算法结束,否则执行步骤②

② 找到节点B的父节点P,判定节点P的父节点是否为节点A。如果是,则对节点B执行zig操作;如果不是,则根据节点B、P以及P的父节点的关系,对节点B执行zig-zig操作或zig-zag操作。

③ 反复执行步骤①和②,直到算法结束。

注意:执行该操作的前提是:A节点必须是B节点的祖先节点。如果该条件不满足,则不可以执行splay操作。另外,如果想将某个节点变为根节点,则只要对空节点NULL和当前节点执行Splay操作即可。

·前驱和后继:

在解答本题过程中,用到操作还有查找某个节点的前驱节点和后继节点。该过程和普通的二叉查找树完全相同,简要说明如下:

对于某个节点的前驱节点,就是它的左孩子的“最右下节点”,即从该节点的左孩子开始,顺着右孩子指针依次向下寻找,最后找到的节点就是其前驱节点:

如果该节点没有左孩子,则其前驱节点就在其祖先节点中,是其“最左上祖先节点”,即从该节点开始,依次顺着父节点指针向上搜索,直到当前节点是其父节点的右孩子,则所求前驱节点为当前的父节点:

对于后继节点,则和前驱节点正好相反,首先寻找其右孩子的“最左下节点”,如果节点没有右孩子,则寻找其“最左上祖先节点”。

·插入:

对于插入操作,和传统的二叉查找树的插入操作完全相同。不同的是,在插入操作完毕后对插入的节点执行Splay操作,使其成为当前Splay树的根节点。这样做的目的是平摊整个算法中不同操作的执行时间,使得整个算法的平摊效率趋近于O(lgn)。

·查找:

查找过程和普通的二叉查找树的过程也相同。在查找完成后,对当前找到的节点执行一次Splay操作,使其成为根节点,原因同上。

·删除:

删除操作比较复杂,也是Splay树的优越性的所在。根据题意,这里的删除的操作是指删除树中所有落在指定区间[a,b]之内的节点。具体的操作步骤是:首先找出结点a的前驱结点aPre,以及结点b的后继节点bNext,然后利用Splay操作将aPre转化为树的根节点,然后将bNext节点转化为aPre的孩子节点。此时由于aPre < bNext, 因此,bNext一定是aPre的右孩子节点,此时,位于bNext的左子树中的节点就一定比apre大,同时又比bNext小,即落入区间[a,b]的节点,此时直接删除bNext的左子树即可。

·虚节点的问题:

删除节点的过程中可能会出现区间边界a,b不在树中的情况。 此时为了保证算法可以正确的进行。可以将a,b作为两个节点插入到树中,然后再执行删除的算法操作。另外在寻找前驱和后继节点的过程中,也会出现查找结果为空的情况,我们可以预先在树中插入一个极小的数和极大的数,这样就保证了所有有效节点的前驱和后继节点均为有效节点。保证算法的顺利进行。

但是注意,上文中插入的这些节点均不是有效的数据节点,是起到辅助作用的“虚”节点,因此在查询过程中,这些节点的值并不是有效的,应该略去。

输入输出格式:

    输入:

第1行:1个正整数n,表示操作数量;

第2..n+1行:可能包含下面3种规则:

1个字母‘I‘,紧接着1个数字k,表示插入一个数字k到树中,保证每个k都不相同。

1个字母‘Q‘,紧接着1个数字k。表示询问树中不超过k的最大数字;

1个字母‘D‘,紧接着2个数字a,b,表示删除树中在区间[a,b]的数。

输出:

若干行:每行1个整数,表示针对询问的回答,保证一定有合法的解。

数据范围:

100≤n≤200,000

1≤k≤1,000,000,000

程序代码:

/****************************************************/
/* File        : Hiho_Week_103                      */
/* Author      : Zhang Yufei                        */
/* Date        : 2016-07-09                         */
/* Description : HihoCoder ACM program. (submit:g++)*/
/****************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

/*
 * Define the node in splay tree.
 * Parameters:
 *		@value: The value of this node.
 *		@max: The max value in the sub-tree.
 *		@tag: Mark if the node is real or fake.
 *		@left & @right: The left and right child.
 *		@parent: The parent node of this node.
 */
typedef struct node {
	int value;
	int max;
	int tag;
	struct node *left, *right;
	struct node *parent;
} node;

// The root of splay tree.
node* root;

/*
 * This function define the left rotate operation.
 * Parameters:
 *		@cur: The current node to rotate.
 * Returns:
 *		None.
 */
void left_rotate(node* cur) {
	node* right = cur->right;
	node* right_left = right->left;
	node* parent = cur->parent;

	right->parent = cur->parent;
	if(parent != NULL) {
		if(cur == parent->left) {
			parent->left = right;
		} else {
			parent->right = right;
		}
	} else {
		root = right;
	}

	right->left = cur;
	cur->parent = right;

	cur->right = right_left;
	if(right_left != NULL) {
		right_left->parent = cur;
	}

	if(cur->tag == 1) {
		cur->max = cur->value;
	} else {
		cur->max = 0;
	}
	if(cur->left != NULL) {
		if(cur->left->max > cur->max) {
			cur->max = cur->left->max;
		}
	}
	if(cur->right != NULL) {
		if(cur->right->max > cur->max) {
			cur->max = cur->right->max;
		}
	}

	if(right->tag == 1) {
		right->max = right->value;
	} else {
		right->max = 0;
	}
	if(right->left != NULL) {
		if(right->left->max > right->max) {
			right->max = right->left->max;
		}
	}
	if(right->right != NULL) {
		if(right->right->max > right->max) {
			right->max = right->right->max;
		}
	}
}

/*
 * This function define the right rotate operation.
 * Parameters:
 *		@cur: The current node to rotate.
 * Returns:
 *		None.
 */
void right_rotate(node* cur) {
	node* left = cur->left;
	node* left_right = left->right;
	node* parent = cur->parent;

	left->parent = cur->parent;
	if(parent != NULL) {
		if(cur == parent->left) {
			parent->left = left;
		} else {
			parent->right = left;
		}
	} else {
		root = left;
	}

	left->right = cur;
	cur->parent = left;

	cur->left = left_right;
	if(left_right != NULL) {
		left_right->parent = cur;
	}

	if(cur->tag == 1) {
		cur->max = cur->value;
	} else {
		cur->max = 0;
	}
	if(cur->left != NULL) {
		if(cur->left->max > cur->max) {
			cur->max = cur->left->max;
		}
	}
	if(cur->right != NULL) {
		if(cur->right->max > cur->max) {
			cur->max = cur->right->max;
		}
	}

	if(left->tag == 1) {
		left->max = left->value;
	} else {
		left->max = 0;
	}
	if(left->left != NULL) {
		if(left->left->max > left->max) {
			left->max = left->left->max;
		}
	}
	if(left->right != NULL) {
		if(left->right->max > left->max) {
			left->max = left->right->max;
		}
	}
}

/*
 * This function define the zig operation.
 * Parameters:
 *		@cur: The current node to operate.
 * Returns:
 *		None.
 */
void zig(node* cur) {
	node* p = cur->parent;
	if(cur == p->left) {
		right_rotate(p);
	} else {
		left_rotate(p);
	}
}

/*
 * This function define the zig-zig operation.
 * Parameters:
 *		@cur: The current node to operate.
 * Returns:
 *		None.
 */
void zig_zig(node* cur) {
	node *p = cur->parent;
	node *pp = p->parent;

	if(cur == p->left) {
		right_rotate(pp);
		right_rotate(p);
	} else {
		left_rotate(pp);
		left_rotate(p);
	}
}

/*
 * This function define the zig-zag operation.
 * Parameters:
 *		@cur: The current node to operate.
 * Returns:
 *		None.
 */
void zig_zag(node* cur) {
	node *p = cur->parent;
	node *pp = p->parent;

	if(cur == p->left) {
		right_rotate(p);
		left_rotate(pp);
	} else {
		left_rotate(p);
		right_rotate(pp);
	}
}

/*
 * This function define the splay operation.
 * Parameters:
 *		@cur: The current node to operate.
 *		@dst: The destination node.
 *			  It's the parent node of @cur in final state.
 * Returns:
 *		None.
 */
void splay(node* cur, node* dst) {
	while(cur->parent != dst) {
		node *p = cur->parent;
		if(p->parent == dst) {
			zig(cur);
		} else {
			node* pp = p->parent;
			if(cur == p->left && p == pp->left ||
				cur == p->right && p == pp->right) {
				zig_zig(cur);
			} else {
				zig_zag(cur);
			}
		}
	}
}

/*
 * This function find the preview node of the current node.
 * Parameters:
 *		@cur: The current node to search.
 * Returns:
 *		The preview node of the current node.
 */
node* preview(node* cur) {
	node* pre = cur->left;
	if(pre != NULL) {
		while(pre->right != NULL) {
			pre = pre->right;
		}
	} else {
		pre = cur;
		while(pre != NULL) {
			if(pre->parent != NULL &&
				pre == pre->parent->right) {
				pre = pre->parent;
				break;
			}
			pre = pre->parent;
		}
	}

	return pre;
}

/*
 * This function find the successor node of the current node.
 * Parameters:
 *		@cur: The current node to search.
 * Returns:
 *		The successor node.
 */
node* successor(node *cur) {
	node *suc = cur->right;
	if(suc != NULL) {
		while(suc->left != NULL) {
			suc = suc->left;
		}
	} else {
		suc = cur;
		while(suc != NULL) {
			if(suc->parent != NULL &&
				suc == suc->parent->left) {
				suc = suc->parent;
				break;
			}
			suc = suc->parent;
		}
	}

	return suc;
}

/*
 * This function insert a node into the splay tree.
 * Parameters:
 *		@ins: The node to insert.
 * Returns:
 *		The inserted node.
 */
node* insert(node* ins) {
	node* p = root;
	node* pre = NULL;

	while(p != NULL) {
		pre = p;
		if(ins->tag == 1) {
			if(p->max < ins->value) {
				p->max = ins->value;
			}
		}
		if(ins->value > p->value) {
			p = p->right;
		} else if(ins->value < p->value) {
			p = p->left;
		} else {
			if(ins->tag == 1) {
				p->value = ins->value;
				p->max = p->value;
				p->tag = 1;
			}
			splay(p, NULL);
			free(ins);
			ins = p;
			return ins;
		}
	}

	if(pre != NULL) {
		if(pre->value > ins->value) {
			pre->left = ins;
		} else {
			pre->right = ins;
		}
		ins->parent = pre;
	} else {
		root = ins;
	}

	splay(ins, NULL);

	return ins;
}

/*
 * This function search the node according to the given range.
 * Parameters:
 * 		@s & @e: The left and right edge of range.
 * Returns:
 *		The root of the sub-tree which contains nodes in the
 *		given range.
 */
node* search(int s, int e) {
	node* s_node = (node*) malloc(sizeof(node));
	s_node->value = s;
	s_node->max = 0;
	s_node->tag = 0;
	s_node->left = s_node->right = s_node->parent = NULL;

	node* e_node = (node*) malloc(sizeof(node));
	e_node->value = e;
	e_node->max = 0;
	e_node->tag = 0;
	e_node->left = e_node->right = e_node->parent = NULL;

	s_node = insert(s_node);
	e_node = insert(e_node);

	node* s_pre = preview(s_node);
	node* e_next = successor(e_node);

	splay(s_pre, NULL);
	splay(e_next, s_pre);	

	return e_next->left;
}

/*
 * This function delete the nodes according to given range.
 * Parameters:
 *		@s & @e: The left and right edge of range.
 * Returns:
 *		None.
 */
void remove(int s, int e) {
	node *del = search(s, e);
	if(del != NULL) {
		node* p = del->parent;
		if(p != NULL) {
			if(p->left == del) {
				p->left = NULL;
			} else {
				p->right = NULL;
			}
		} else {
			root = NULL;
		}

		while(p != NULL) {
			if(p->tag == 1) {
				p->max = p->value;
			} else {
				p->max = 0;
			}
			if(p->left != NULL) {
				if(p->max < p->left->max) {
					p->max = p->left->max;
				}
			}
			if(p->right != NULL) {
				if(p->max < p->right->max) {
					p->max = p->right->max;
				}
			}
			p = p->parent;
		}
	}
}

/*
 * The main program.
 */
int main(void) {
	int n;
	scanf("%d", &n);

	node* min = (node*) malloc(sizeof(node));
	min->value = 0;
	min->max = 0;
	min->tag = 0;
	min->left = min->right = min->parent = NULL;

	node* max = (node*) malloc(sizeof(node));
	max->value = 1000000001;
	max->max = 0;
	max->tag = 0;
	max->left = max->right = max->parent = NULL;

	insert(min);
	insert(max);

	for(int i = 0; i < n; i++) {
		char op;
		getchar();
		scanf("%c", &op);
		if(op == 'I') {
			int k;
			scanf("%d", &k);

			node *ins = (node*) malloc(sizeof(node));
			ins->value = k;
			ins->max = k;
			ins->tag = 1;
			ins->left = ins->right = ins->parent = NULL;

			insert(ins);
		} else if(op == 'Q') {
			int k;
			scanf("%d", &k);

			node* r = search(1, k);
			printf("%d\n", r->max);
		} else if(op == 'D') {
			int a, b;
			scanf("%d %d", &a, &b);

			a = a > 1 ? a : 1;
			b = b < 1000000000 ? b : 1000000000;

			remove(a, b);
		}
	}

	return 0;
}
时间: 2024-08-04 06:59:11

ACM:平衡树(2)——Splay的相关文章

P3391 【模板】文艺平衡树(Splay)新板子

P3391 [模板]文艺平衡树(Splay) 题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,

【BZOJ】3223: Tyvj 1729 文艺平衡树(splay)

http://www.lydsy.com/JudgeOnline/problem.php?id=3223 默默的.. #include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream> #include <algorithm> #include <queue> #include <set> #in

P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n?1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

平衡树(splay)

求区间最大值模板 1 struct node{ 2 int f[maxn];//父亲结点 3 int ch[maxn][2];//左右孩子 4 int key[maxn];//结点i代表的数字 5 int cnt[maxn];//结点i出现的次数,也可以为全值.平衡树没有相同值的结点,所以如果出现了相同值时,cnt[i]++,或者cnt[i] += key[i] 6 int siz[maxn];//包括i的子树的大小 7 int val[maxn]; 8 int Max[maxn]; 9 int

[知识点]平衡树之Splay

// 此博文为迁移而来,写于2015年7月18日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6rg.html 1.前言 这玩意儿真的搞了我好久,当然前一阵子一直都没有去管它,最近直接参照了YML(@YMDragon)的程序,感觉还是不错的,大概看得懂了,就是逻辑关系有点扯.好,废话不多说…… 2.概念 平衡树的全称为:平衡二叉搜索树,功能很强大,带来的后果就是代码非常复杂.首先大家应该都知道二叉搜索树是什么了,那

洛谷P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

洛谷P3391 【模板】文艺平衡树(Splay)(FHQ Treap)

题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n?1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

文艺平衡树(splay模板)

题干:splay模板,要求维护区间反转. splay是一种码量小于treap,但支持排名,前驱后继等treap可求的东西,也支持区间反转的平衡树. 但是有两个坏处: 1.splay常数远远大于treap以及stl中的set. 2.没有可持久化splay,但有可持久化treap. 下面是代码: 1.pushup以及pushdown pushup用于维护某点所在子树大小. void pushup(int u) { tr[u].siz = tr[tr[u].ch[0]].siz + tr[tr[u].