联合解题报告:普通平衡树

版权声明:转载请附带原文链接,请勿随意删除原文内容,允许少量格式和/或内容修改,谢谢! https://blog.csdn.net/weixin_37661548/article/details/87560882

目录


【模板】普通平衡树

参考代码

大部分注释来源

/*
鸣谢:小蒟蒻yyb
《Splay入门解析【保证让你看不懂(滑稽)】》https://blog.csdn.net/qq_30974369/article/details/77587168
*/
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;

int n,op;

template<class T> void qread(T &sum)
{
	sum = 0;
	register int sym = 1;
	register char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-') sym = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		sum = (sum << 1) + (sum << 3) + ch - '0';
		ch = getchar();
	}
	sum *= sym;
}

template<class T> void qwrite(const T x,int ori)
{
	if (x < 0)
	{
		putchar('-');
		qwrite(-x,ori);
	}
	else
	{
		if (x >= 10) qwrite(x / 10,ori);
		putchar(x % 10 + '0');
	}
	if (x == ori) putchar('\n');
}

namespace Splay
{
	/*
	怎么背模板呢?qwq
	---------函数----------方法------------------------操作对象
	inline pushUp() -------------------------------------节点
	inline bool get() //两个短的直接记!-----------------节点
	inline rotate() //最基本操作 ------------------------节点
	splay() //不停rotate()变成splay() -------------------节点
	find() //splay()和find()功能类似 --------------------节点
	int Next() //find next好不好记qwq -------------------数字
	insert() --------------------------------------------数字
	remove() //插入删除是一对 ---------------------------数字
	int queryKth() --------------------------------------数字
	inline int queryRank() //两个查询是一对qwq ----------数字
	//一共10个函数qwq
	*/
	int son[MAXN][2],fa[MAXN],cnt[MAXN],size[MAXN],val[MAXN];
	//爸爸+儿子+两计数(节点个数+重复个数)+记录值
	int Root,nid;

	inline void pushUp(int x)
	{
		size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x]; //还要加上自己本身重复的次数。
	}

	inline bool get(int x)
	{
		return son[fa[x]][1] == x; //1代表右儿子。如果x是右儿子,恰好返回1.
	}

	inline void rotate(int x)
	{
		int y = fa[x],z = fa[fa[x]],which = get(x),w = son[x][which ^ 1];
		son[y][which] = w; //X的 X原来在Y的 相对的 那个儿子 变成了 Y原来是X的那个儿子
		fa[w] = y;
		son[z][get(y)] = x; //x变到原来y的位置。
		fa[x] = z;
		son[x][which ^ 1] = y; //Y变成了 X原来在Y的 相对的那个儿子
		fa[y] = x;
		//注意!!!第三句只能放最底下,第二句必须放第三句上面,第一句可以随便放!!!
		//因为第三句修改了y是fa[y]的左右儿子。
		//记忆:yx zx xy
		pushUp(y);
		pushUp(x); //注意!!!顺序不能换,因为此时y是x的儿子。要从底向上pushUp.
	}

	void splay(int x,int aim = 0)
	{
		while (fa[x] != aim) //一直旋转到x成为goal的儿子(注意是儿子!)
		{
			int y = fa[x],z = fa[fa[x]]; //注意!!!y和z必须放在循环内,因为每次rotate()后他们都会更新。
			if (z != aim)
			{
				if (get(x) == get(y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
		if (aim == 0) Root = x; //不过如果aim等于0或不带参,就转到根节点。
	}

	void find(int x) //如果不存在这个数呢,返回这个数的前驱或者后继节点。
	{
		int cur = Root;
		while (val[cur] != x && son[cur][x > val[cur]])
		{
			cur = son[cur][x > val[cur]];
		}
		splay(cur);
		/*
		从根节点开始,左侧都比他小,右侧都比他大,
		所以只需要相应的往左/右递归.
		如果当前位置的val已经是要查找的数,
		那么直接把他Splay到根节点,方便接下来的操作.
		*/
	}

	int Next(int x,int f) //查找x的前驱(0)或者后继(1).
	{
		find(x);
		if ((val[Root] > x && f) || (val[Root] < x && !f)) return Root;
		//假如x存在,则上面的if语句并不会执行。
		//假如x不存在,那么它的前驱或后继就是Root。如果我们的查询恰好与x和Root的关系吻合,直接返回Root
		//即可。如果相反,按照下面程序也可以找到前驱后继。
		int cur = son[Root][f]; //查找后继的话在右儿子上找,前驱在左儿子上找。
		while (son[cur][f ^ 1]) cur = son[cur][f ^ 1]; //要反着跳转,否则会越来越大(越来越小)
		return cur;
	}

	void insert(int x)
	{
		int cur = Root,f = 0; //记录cur的父节点。
		while (val[cur] != x && cur)
		{
			f = cur; 
			cur = son[cur][x > val[cur]];
		}
		if (cur) cnt[cur]++;
		else
		{
			cur = ++nid;
			if (f) son[f][x > val[f]] = cur; //当f为0时,cur就是根节点。0是虚拟的,令0为根节点的爸爸。
											 //仅当cur不是根节点时,才有必要建立他与他爸爸之间的关系。
			val[cur] = x;
			fa[cur] = f;
			son[cur][0] = son[cur][1] = 0;
			size[cur] = cnt[cur] = 1;
		}
		splay(cur); //把当前位置移到根,保证结构的平衡.
		/*
		往Splay中插入一个数,
		类似于Find操作,只是如果是已经存在的数,就可以直接在查找
		到的节点的进行计数.如果不存在,在递归的查找过程中,
		会找到他的父节点的位置,然后就会发现底下没有啦。。。
		所以这个时候新建一个节点就可以了.
		*/
	}

	void remove(int x)
	{
		int last = Next(x,0),nxt = Next(x,1);
		splay(last); //find()也是提到根,但find()提的是某个数代表的节点,而splay()提的直接是某个节点。
		splay(nxt,last);
		int del = son[nxt][0];
		if (cnt[del] > 1)
		{
			cnt[del]--;
			splay(del);
		}
		else son[nxt][0] = 0;
		/*
		现在就很简单啦,首先找到这个数的前驱,把他Splay到根节点
		然后找到这个数后继,把他旋转到前驱的底下,比前驱大的数是后继,
		在右子树,比后继小的且比前驱大的有且仅有当前数,在后继的左子树上面,
		因此直接把当前根节点的右儿子的左儿子删掉就可以啦qwq
		*/
	}

	int queryKth(int k) //查询第k小的数是多少。(注意取val[]!)
	{
		int cur = Root;
		while (true)
		{
			if (son[cur][0] && k <= size[son[cur][0]]) cur = son[cur][0];
			else if (size[son[cur][0]] + cnt[cur] >= k) return cur;
			else
			{
				k -= size[son[cur][0]] + cnt[cur];
				cur = son[cur][1];
			}
			//这个和主席树差不多了嘛......
		}
	}

	inline int queryRank(int x)
	{
		find(x);
		return size[son[Root][0]]; //如果我们找到了权值为x的节点,那么答案就是他的左子树的大小.
								   //和下面求kth时不同,由于边框是不算数的(假设有数-inf,1,问第一小的数)
								   //,1的左子树节点个数就是1,不用加1。
	}
}
//若splay()只带一个参,那么是将某节点转到根;
//若带两个参,是将某节点转到目标节点的儿子;
//find()是将某个数代表的节点转到根。
using namespace Splay;

void init()
{
	scanf("%d",&n);
	insert(0x3f3f3f3f);
	insert(-0x3f3f3f3f); //加边框。
}

void work()
{
	int x;
	for (int i = 1; i <= n; ++i)
	{
		qread(op), qread(x);
		switch (op)
		{
			case 1 :
				{
					insert(x);
					break;
				}
			case 2 :
				{
					remove(x);
					break;
				}
			case 3 :
				{
					printf("%d\n",queryRank(x));
					break;
				}
			case 4 :
				{
					printf("%d\n",val[queryKth(x + 1)]); //因为加了一个边框,所以排名要+1。
					break;
				}
			case 5 :
				{
					printf("%d\n",val[Next(x,0)]);
					break;
				}
			case 6 :
				{
					printf("%d\n",val[Next(x,1)]);
					break;
				}
		}
	}
}

int main()
{
	init();
	work();
	return 0;
}

注意点

参见:

  • rotate()
  • splay()
  • queryKth()
  • queryRank()

T2 [HNOI2002]营业额统计

参考代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 50000;

int n,ans;
int turnover[MAXN];

namespace FastIO
{
	template<class T> void qread(T &sum)
	{
		sum = 0;
		register int sym = 1;
		register char ch = getchar();
		while (ch < '0' || ch > '9')
		{
			if (ch == '-') sym = -1;
			ch = getchar();
		}
		while (ch >= '0' && ch <= '9')
		{
			sum = (sum << 1) + (sum << 3) + ch - '0';
			ch = getchar();
		}
		sum *= sym;
	}
	
	template<class T> void qwrite(const T x)
	{
		if (x < 0)
		{
			putchar('-');
			qwrite(-x);
		}
		else
		{
			if (x >= 10) qwrite(x / 10);
			putchar(x % 10 + '0');
		}
	}
}
using namespace FastIO;

namespace Splay
{
	int Root, nid;
	int son[MAXN][2], fa[MAXN], cnt[MAXN], size[MAXN], value[MAXN];
	
	inline void pushUp(int x)
	{
		size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x];
	}
	
	inline bool get(int x)
	{
		return son[fa[x]][1] == x;
	}
	
	inline void rotate(int x)
	{
		int y = fa[x], z = fa[y], k = get(x);
		son[y][k] = son[x][k ^ 1];
		fa[son[x][k ^ 1]] = y;
		son[z][get(y)] = x;
		fa[x] = z;
		son[x][k ^ 1] = y;
		fa[y] = x;
		pushUp(y);
		pushUp(x);
	}
	
	void splay(int x,int aim)
	{
		while (fa[x] != aim)
		{
			int y = fa[x], z = fa[y];
			if (z != aim)
			{
				if (get(x) == get(y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
		if (!aim) Root = x;
	}
	
	void find(int x)
	{
		int cur = Root;
		while (value[cur] != x && son[cur][x > value[cur]])
			cur = son[cur][x > value[cur]];
		splay(cur,0);
	}
	
	int Next(int x,int f)
	{
		find(x);
		if ((value[Root] > x && f) || (value[Root] < x && !f)) return Root;
		int cur = son[Root][f];
		while (son[cur][f ^ 1]) cur = son[cur][f ^ 1];
		return cur;
	}
	
	void insert(int x)
	{
		int cur = Root, f = 0;
		while (cur && value[cur] != x)
		{
			f = cur;
			cur = son[cur][x > value[cur]];
		}
		if (cur) cnt[cur]++;
		else
		{
			cur = ++nid;
			if (f) son[f][x > value[f]] = cur;
			fa[cur] = f;
			value[cur] = x;
			son[cur][0] = son[cur][1] = 0;
			size[cur] = cnt[cur] = 1;
		}
		splay(cur,0);
	}
	
	void remove(int x)
	{
		int pre = Next(x,0), suc = Next(x,1);
		splay(pre,0);
		splay(suc,pre);
		int del = son[suc][0];
		if (cnt[del] > 1)
		{
			cnt[del]--;
			splay(del,0);
		}
		else son[suc][0] = 0;
	}
	
	int queryKth(int k)
	{
		int cur = Root;
		while (true)
		{
			if (size[son[cur][0]] >= k) cur = son[cur][0];
			else if (size[son[cur][0]] + cnt[cur] >= k) return cur;
			else
			{
				k -= size[son[cur][0]] + cnt[cur];
				cur = son[cur][1];
			}
		}
	}
	
	int queryRank(int x)
	{
		find(x);
		return size[son[Root][0]];
	}
	
	inline bool ifgroup(int x)
	{
		find(x);
		if (cnt[Root] > 1) return true;
		return false;
	}
}
using namespace Splay;

void init()
{
	qread(n);
	for (int i = 1;i <= n;++i) qread(turnover[i]);
	insert(0x3f3f3f3f);
	insert(-0x3f3f3f3f);
}

void work()
{
	for (int i = 1;i <= n;++i)
	{
		insert(turnover[i]);
		if (i == 1)
		{
			ans += turnover[i];
			continue;
		}
		if (ifgroup(turnover[i])) continue;
		int pre = Next(turnover[i],0);
		int suc = Next(turnover[i],1);
		ans += min(abs(value[pre] - turnover[i]),abs(value[suc] - turnover[i]));
	}
	qwrite(ans);
}

int main()
{
	init();
	work();
	return 0;
}

分析

  • 由于营业额是按照时间顺序插入的,因此每次只需在现有的Splay中找到与当前数最接近的数。
  • 找到当前数的前驱后继,比一下绝对值之差,取较小者加入答案即可。
  • 注意重复的数,如果有重复直接跳过即可。

T3 [HNOI2004]宠物收养场

参考代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000, P = 1000000;

int n,ans,pet,man;
int a[MAXN],b[MAXN];

namespace Splay
{
	int Root, nid;
	int son[MAXN][2], fa[MAXN], cnt[MAXN], size[MAXN], value[MAXN];
	
	inline void pushUp(int x)
	{
		size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x];
	}
	
	inline bool get(int x)
	{
		return son[fa[x]][1] == x;
	}
	
	inline void rotate(int x)
	{
		int y = fa[x], z = fa[y], k = get(x);
		son[y][k] = son[x][k ^ 1];
		fa[son[x][k ^ 1]] = y;
		son[z][get(y)] = x;
		fa[x] = z;
		son[x][k ^ 1] = y;
		fa[y] = x;
		pushUp(y);
		pushUp(x);
	}
	
	void splay(int x,int aim)
	{
		while (fa[x] != aim)
		{
			int y = fa[x], z = fa[y];
			if (z != aim)
			{
				if (get(x) == get(y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
		if (!aim) Root = x;
	}
	
	void find(int x)
	{
		int cur = Root;
		while (value[cur] != x && son[cur][x > value[cur]])
			cur = son[cur][x > value[cur]];
		splay(cur,0);
	}
	
	int Next(int x,int f)
	{
		find(x);
		if ((value[Root] > x && f) || (value[Root] < x && !f)) return Root;
		int cur = son[Root][f];
		while (son[cur][f ^ 1]) cur = son[cur][f ^ 1];
		return cur;
	}
	
	void insert(int x)
	{
		int cur = Root, f = 0;
		while (value[cur] != x && cur)
		{
			f = cur;
			cur = son[cur][x > value[cur]];
		}
		if (cur) cnt[cur]++;
		else
		{
			cur = ++nid;
			if (f) son[f][x > value[f]] = cur;
			fa[cur] = f;
			value[cur] = x;
			son[cur][0] = son[cur][1] = 0;
			size[cur] = cnt[cur] = 1;
		}
		splay(cur,0);
	}
	
	void remove(int x)
	{
		int pre = Next(x,0), suc = Next(x,1);
		splay(pre,0);
		splay(suc,pre);
		int del = son[suc][0];
		if (cnt[del] > 1)
		{
			cnt[del]--;
			splay(del,0);
		}
		else son[suc][0] = 0;
	}
}
using namespace Splay;

void init()
{
	qread(n);
	insert(0x3f3f3f3f);
	insert(-0x3f3f3f3f);
	for (int i = 1;i <= n;++i)
	{
		qread(a[i]);
		qread(b[i]);
	}
}

void work()
{
	for (int i = 1;i <= n;++i) //一棵Splay既可以放宠物也可以放人qwq
	{
		if (a[i]) //来人。
		{
			if (pet) //宠物还有剩余,必须取一个。
			{
				int target = b[i];
				int pre = Next(target,0);
				int suc = Next(target,1);
				if (abs(value[pre] - target) <= abs(value[suc] - target))
				{
					ans = (abs(value[pre] - target) % P + ans % P) % P;
					remove(value[pre]);
				}
				else
				{
					ans = (abs(value[suc] - target) % P + ans % P) % P;
					remove(value[suc]);
				}
				pet--;
			}
			else //人留下等宠物。
			{
				insert(b[i]);
				man++;
			}
		}
		else //来宠物。
		{
			if (man) //人还有剩余,必须取一个。
			{
				int target = b[i];
				int pre = Next(target,0);
				int suc = Next(target,1);
				if (abs(value[pre] - target) <= abs(value[suc] - target))
				{
					ans = (abs(value[pre] - target) % P + ans % P) % P;
					remove(value[pre]);
				}
				else
				{
					ans = (abs(value[suc] - target) % P + ans % P) % P;
					remove(value[suc]);
				}
				man--;
			}
			else //宠物留下等人。
			{
				insert(b[i]);
				pet++;
			}
		}
	}
	qwrite(ans % P);
}

int main()
{
	init();
	work();
	return 0;
}

分析

  • 也是找前驱后继,其余的参见注释。

若有谬误,敬请斧正。

猜你喜欢

转载自blog.csdn.net/weixin_37661548/article/details/87560882
今日推荐