获得关注的好方法。。。
查看原帖
获得关注的好方法。。。
868063
_Coffice_楼主2024/6/29 19:29

splay求条:

#include <iostream>
using namespace std;
//	伸展树是一种二叉排序树,
//	它能在O(log n)内完成插入、查找和删除操作。
//	在伸展树上的一般操作都基于伸展操作:
//	假设想要对一个二叉查找树执行一系列的查找操作,
//	为了使整个查找时间更小,
//	被查频率高的那些条目就应当经常处于靠近树根的位置。
//	于是想到设计一个简单方法, 
//	在每次查找之后对树进行重构,
//	把被查找的条目搬移到离树根近一些的地方。
//	伸展树应运而生。
//	伸展树是一种自调整形式的二叉查找树,
//	它会沿着从某个节点到树根之间的路径,
//	通过一系列的旋转把这个节点搬移到树根去。
const int N = 100005;
struct Node
{
	int sz/*子树大小*/, val/*值*/, ch[2]/*子节点*/, cnt/*出现次数*/;
	int father; // 父节点 
};
Node tree[N];
int tot = 0, root;
bool get(int p) // 返回编号为p的节点是其父亲的左儿子(返回0)或者右儿子(返回1)。
{
	return (tree[tree[p].father].ch[1] == p);
}
void push_up(int p) // 上传
{
	if (p != 0)
	{
		tree[p].sz = 0;
		if (tree[p].ch[0] != 0)
		{
			tree[p].sz += tree[tree[p].ch[0]].sz;
		}
		if (tree[p].ch[1] != 0)
		{
			tree[p].sz += tree[tree[p].ch[1]].sz;
		}
		tree[p].sz += tree[p].cnt;
	}
}
void turn(int p) // 旋转,调换父子关系 
{
	/*
		      A
		     / \
		    B  C
		   / \
		  D  E
		 / \
		F  G
	*/
	// 调换BD,同时保持排序树的性质
	// 原数量关系:F<D<G<B<E<A<C
	/* 结果:
			A
		   / \
		  D  C
		 / \
		F  B
		  / \
		 G  E 
	*/ 
	// 数量关系:F<D<G<B<E<A<C
	// 过程总结:
	// 因为:
	// 若是父亲左子节点,则父亲>自己,父亲在自己右子节点,自己原右子树也会比父亲小(左-中-右先遍历到),归左子树
	// 若是父亲右子节点,则父亲<自己,父亲在自己左子节点,自己原左子树也会比父亲大(左-中-右后遍历到),归右子树 
	// 连接BG(父亲连自己的边连自己的反向儿子)
	// 连接BD(调换父子关系,让父亲做反向儿子)
	// 连接AD(自己与爷爷) 
	int fa = tree[p].father, grand_father = tree[fa].father;
	int tmp = get(p);
	tree[fa].ch[tmp] = tree[p].ch[!tmp];
	tree[tree[fa].ch[tmp]].father = fa;
	tree[p].ch[!tmp] = fa;
	tree[fa].father = p;
	if (grand_father != 0)
	{
		tree[p].father = grand_father;
		if (tree[grand_father].ch[0] == fa)
			tree[grand_father].ch[0] = p;
		else tree[grand_father].ch[1] = p;
	}
	else tree[p].father = 0;
	// 父亲已经成为儿子,先处理父亲 
	push_up(fa);
	push_up(p);
} 
void splay(int p) // 推到根,将访问多的放在上 
{
	// 不断turn 
	// 但当祖父,父亲,自己三点一线时,
	// 一直旋转自己会把父亲、祖父等节点堆在与父连接的反向边上,
	// 造成深度过深,算法退化 
	// 应当先turn父节点,交叉堆叠
	while (tree[p].father != 0)
	{
		if (tree[tree[p].father].father == 0)
		{
			turn(p);
		}
		else
		{
			if (get(p) == get(tree[p].father))
			{
				turn(tree[p].father);
			}
			else turn(p);
		}
	}
	root = p; 
} 
void insert(int v) // 插入v 
{
	if (root == 0) // 空树 
	{
		++tot;
		root = tot;
		tree[root].ch[0] = tree[root].ch[1] = tree[root].father = 0;
		tree[root].val = v;
		tree[root].sz = tree[root].cnt = 1;
		return ;
	} 
	// 否则找该插入在哪里
	int tmp = root;
	while (1)
	{
		if (tree[tmp].val == v) // 找到原有的 
		{
			++tree[tmp].cnt; // 计数器+1
			push_up(tmp);
			push_up(tree[tmp].father); // 更新自己与父亲(可能转父亲),在splay上去更新有关点
			splay(tmp);
			break; 
		}
		int last = tmp;
		if (v < tree[tmp].val)
			tmp = tree[tmp].ch[0];
		else tmp = tree[tmp].ch[1];
		if (tmp == 0) // 找到最底下,直接插入 
		{
			++tot;
			tree[tot].ch[0] = tree[tot].ch[1] = 0;
			tree[tot].val = v;
			tree[tot].sz = tree[tot].cnt = 1;
			tree[tot].father = last;
			if (v < tree[last].val)
				tree[last].ch[0] = tot;
			else tree[last].ch[1] = tot;
			push_up(last);
			splay(tot); // 同上
			break; 
		}
	} 
}
int find(int v) // 查找v的排名 
{
	// 找到自己,统计小于自己的个数+1
	int ans = 0, now = root;
	while (1)
	{
		if (v < tree[now].val)
		{
			// 左子树,统计不变
			now = tree[now].ch[0]; 
		}
		else if (v >= tree[now].val) // 大于等于时,小于部分统计 
		{
			// 右子树
			if (tree[now].ch[0] != 0) // 统计左子树
				ans += tree[tree[now].ch[0]].sz;
			if (v == tree[now].val)
			{
				splay(now); // 推上去,下次方便
				return ans+1; 
			}
			else // 大于时,再统计根节点
				ans += tree[now].cnt; 
			now = tree[now].ch[1]; 
		}
	} 
}
int kth(int k) // 查询排名为k的点
{
	int now = root;
	while (1)
	{
		if (tree[now].ch[0] != 0 && k <= tree[tree[now].ch[0]].sz) // 左子树
		{
			now = tree[now].ch[0];
		} 
		else // 根节点或右子树 
		{
			int tmp = 0; // 到根节点的排名
			if (tree[now].ch[0] != 0)
				tmp += tree[tree[now].ch[0]].sz;
			tmp += tree[now].cnt;
			if (k <= tmp) // 在这区间
			{
				return tree[now].val; 
			} 
			else 
			{
				k -= tmp;
				now = tree[now].ch[1];
			}
		}
	}
}
int pre() // 根的前驱 
{
	int now = tree[root].ch[0];
	while (tree[now].ch[1] != 0)
	{
		now = tree[now].ch[1];
	}
	return now;
}
int nxt() // 根的后缀
{
	int now = tree[root].ch[1];
	while (tree[now].ch[0] != 0)
		now = tree[now].ch[0];
	return now; 
} 
void del(int v) // 删除v 
{
	// 1.旋到根
	find(v); // 自动旋到根
	// 2.分类
	if (tree[root].cnt > 1)
	{
		tree[root].cnt--; // 不止一个,直接-1
		push_up(root); //  更新
	}
	else if (tree[root].ch[0] == 0 && tree[root].ch[1] == 0) // 没有儿子,直接清空
	{
		tree[root].cnt = tree[root].sz = tree[root].val = tree[root].father = 0;
		root = 0;
	} 
	else if (tree[root].ch[0] == 0) // 无左儿子,用右儿子做根
	{
		int last = root;
		root = tree[root].ch[1];
		tree[root].father = 0;
		tree[last].father = tree[last].ch[0] = tree[last].ch[1] = tree[last].val = tree[last].sz = tree[last].cnt = 0;
	} 
	else if (tree[root].ch[1] == 0) // 同理
	{
		int last = root;
		root = tree[root].ch[0];
		tree[root].father = 0;
		tree[last].father = tree[last].ch[0] = tree[last].ch[1] = tree[last].val = tree[last].sz = tree[last].cnt = 0;
	} 
	else
	{
		// root有两个儿子,我们要拿root的前驱作新根,将原先root的右子树接到新root的右子树上
		// (由于选的是前驱,原先root一定没有左子树)。
		int last = root, thepre = pre();
		splay(thepre); // 替换根
		tree[tree[last].ch[1]].father = root; // 将右子树接上
		tree[root].ch[1] = tree[last].ch[1];
		tree[root].cnt = tree[root].sz = tree[root].val = tree[root].father = tree[root].ch[0] = tree[root].ch[1] = 0; // 清除
		push_up(root); // 更新 
	}
}
signed main() // 主程序
{
	ios::sync_with_stdio(false); // 加速读入
	cin.tie(0), cout.tie(0); // 加速读入
	int n; // 操作次数
	cin >> n; // 读入数据
	for(int i=1;i<=n;i++)
	{
		int op, x; 
		cin >> op >> x; // 输入操作的编号和操作数据
		if(op == 1) insert(x); // 操作1:新建节点
		if(op == 2) del(x); // 操作2:删除节点
		if(op == 3) 
		{
			insert(x); // 加入,防止其中没有
			cout << find(x) << '\n'; // 操作3:查询x数的排名(排名定义为比当前数小的数的个数+1)
			del(x); // 删除,防止影响正常操作
		}
		if(op == 4) cout << kth(x) << '\n'; // 操作4:查询排名为x的数
		if(op == 5) 
		{
			insert(x); // 加入,防止其中没有
			cout << tree[pre()].val << '\n'; // 操作5:求x的前驱(前驱定义为小于x,且最大的数)
			del(x); // 删除,防止影响正常操作
		}
		if(op == 6) 
		{
			insert(x); // 加入,防止其中没有
			cout << tree[nxt()].val << '\n'; // 操作6:求x的后继(后继定义为大于x,且最小的数)
			del(x); // 删除,防止影响正常操作
		}
	}
	return 0; // 结束程序
}

玄关。效果

2024/6/29 19:29
加载中...