某巨文艺平衡树(Splay模板)

洛谷 P3391

/*Splay只记模板是很困难的,而且真正运用时易生疏出错,所以必须理解,在看代码前先弄懂
Splay的原理,这篇代码是带注释的Splay模板,题目来自洛谷P3391 ———————————by 520*/
#include<bits/stdc++.h>
#define il inline
using namespace std;
const int N = 100005;
il int gi()
{
	int a = 0; char x = getchar(); bool f = 0;
	while ((x<'0' || x>'9') && x != '-')x = getchar();
	if (x == '-')x = getchar(), f = 1;
	while (x >= '0'&&x <= '9')a = a * 10 + x - 48, x = getchar();
	return f ? -a : a;
}
int n, m, tot, root, siz[N], fa[N], lazy[N], key[N], tree[N][2], ans[N];
/*root为根节点,siz存储子树节点数,fa存储父节点,lazy是懒惰标记用来标记区间翻转操作,key数组存储原数列,tree为
splay树,ans存储答案*/
il void pushup(int rt)  //作用类似与线段树
{
	int l = tree[rt][0], r = tree[rt][1];         //pushup作用是将子树的节点个数更新给根节点
	siz[rt] = siz[l] + siz[r] + 1;
}
il void pushdown(int now)
{
	if (lazy[now]) {
		lazy[tree[now][0]] ^= 1;
		lazy[tree[now][1]] ^= 1;               /*pushdown作用是下放懒惰标记,若某一节点所在子树(即某一区间)被翻转
		,则将懒惰标记下放给两个儿子节点,交换左右儿子位置(中序遍历,交换左右儿子后相当于翻转)并对所在子树根节
		点的标记清0,*/
		swap(tree[now][0], tree[now][1]);
		lazy[now] = 0;
	}
}
il int getson(int x) { return tree[fa[x]][1] == x; }  //getson判断x是其父亲的右儿子还是左儿子
il void rotate(int x)       //旋转操作,直接写在一个函数里,可以称为上旋
{
	int y = fa[x], z = fa[y], b = getson(x), c = getson(y), a = tree[x][!b];  /*y是x的父节点,z是y的父节点,getson解释过了。
		特别解释一下a,a为旋转时需要移动的子树,若x为左儿子则右旋时要将x的右子树移动,同理若x为右儿子则左旋时要
		将x的左子树移动,所以这里a=tree[x][!b]*/
	if (z)tree[z][c] = x; else root = x; fa[x] = z; /*若z不为根节点,则用x替代y的位置;若z为根节点,则将x变为根节点。*/
	if (a)fa[a] = y; tree[y][b] = a; /*若存在要移动的子树a,则把a和y相连,取代原来x的位置*/
	tree[x][!b] = y; fa[y] = x;  /*!b的原因:若x为左儿子则旋转后y为x的右儿子,若x为右儿子则旋转后y为x的左儿子。记得将y
							指向x*/
	pushup(y); pushup(x);   /*旋转后,对被移动了的y和x更新它们各自的子树节点数*/
}
il void splay(int x, int i)
{
	while (fa[x] != i) {          //只要x没有旋转到需要的点下面,则一直旋,注意根节点的父亲为虚点0
		int y = fa[x], z = fa[y];
		if (z == i)rotate(x);     //若x的爷爷是i,则只需旋一次
		else {
			if (getson(x) == getson(y)) { rotate(y); rotate(x); }   /*若x和y为相同偏向,则进行Zig-Zig或Zag-Zag操作*/
			else { rotate(x); rotate(x); }   /*否则进行Zig-Zag或Zag-Zig操作*/
				/*注意rotate函数中已经包含了这四种操作情况了*/
		}
	}
}
il int find(int x)    //查找x的位置
{
	int now = root;    //从根节点往下
	while (1) {
		pushdown(now);    //本次操作要将前面的标记进行翻转
		if (tree[now][0] && x <= siz[tree[now][0]])now = tree[now][0];   //若存在左子树且x小于等于左子树的节点数,则x在左子树上
		else {
			int tmp = (tree[now][0] ? siz[tree[now][0]] : 0) + 1;   //往右子树找,+1代表加上这个子树的根节点
			if (x == tmp)return now;      //若找到了x,返回它的位置
			x -= tmp;      //否则x减去根节点右子树以外的节点数,这个画图能理解,因为siz值并不是直接的x的值
			now = tree[now][1];  //将原来根节点的右儿子赋为新的根节点,继续递归查找x位置
		}
	}
}
il int build(int l, int r, int rt)   //建树过程和线段树类似
{
	int now = l + r >> 1;
	fa[now] = rt;
	key[now] = ans[now];        //key存原数组1到n,准确说是0到n+1,原因是主函数里的预处理
	if (l < now)tree[now][0] = build(l, now - 1, now);
	if (r > now)tree[now][1] = build(now + 1, r, now);
	pushup(now);   //记得pushup
	return now;
}
il void print(int now)   //输出时中序遍历,按左根右输出
{
	pushdown(now);   //记得要翻转
	if (tree[now][0])print(tree[now][0]);   //因为中序遍历左根右,所以递归根节点左子树到第一个数的位置
	ans[++tot] = key[now];   //回溯时存储答案,注意我们翻转操作的是原数组下标
	if (tree[now][1])print(tree[now][1]);   //同理递归根节点的右子树
}
int main()
{
	n = gi(), m = gi(); int x, y;
	for (int i = 1; i <= n + 2; i++)ans[i] = i - 1;    /*因为取出操作区间时旋转的是x的前驱和y的后驱,所以预处理时第i个点
		存的是i的前驱*/
	root = build(1, n + 2, 0);
	while (m--)
	{
		x = gi(), y = gi();
		x = find(x), y = find(y + 2);  /*查找x的前驱所在的位置,和y后驱所在的位置,因为预处理时ans存的是前趋,
								所以直接查找x,而y的后驱变成了y+2*/
		splay(x, 0); splay(y, x);  /*将x前驱上旋至根节点,y的后驱上旋成根节点右儿子的左子树*/
		lazy[tree[tree[root][1]][0]] ^= 1;//经过旋转后,此时根节点的右儿子的左子树就是需要翻转的区间,所以lazy标记
	}
	print(root);
	for (int i = 1; i <= n; i++)printf("%d ", ans[i + 1]);   //输出时将前驱还原为原数
	return 0;
}

 

poj 3580&BZOJ 1895

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int INF = 1e9;
int n, m;
int ch[maxn][2]; //0做孩子, 1右孩子
int f[maxn]; //每个节点的父亲
int sz[maxn]; //每个节点为根子树的大小
int val[maxn]; //这个节点所表示的值
int cnt[maxn]; //这个节点所表示值的数量
int mi[maxn]; //这个节点子树的最小值
int rev[maxn]; //反转标记
int lazy[maxn]; //延迟标记
int root;  // splay的根
int tot; //树所有的节点数量
void Swap(int &x, int &y)
{
	x ^= y; y ^= x; x ^= y;
}
int Min(int x, int y)
{
	return x < y ? x : y;
}
void update_rev(int x) //更新反转
{
	if (!x) return;
	Swap(ch[x][0], ch[x][1]);
	rev[x] ^= 1;  //如果这一层曾经被转过下面就不用转了, 把rev取消了
}
void update_add(int x, int v)
{
	if (x) lazy[x] += v, val[x] += v, mi[x] += v;
}
void newnode(int rt, int v, int fa)
{
	f[rt] = fa; sz[rt] = 1;
	val[rt] = mi[rt] = v;
	ch[rt][0] = ch[rt][1] = rev[rt] = lazy[rt] = 0; //加点的时候把所有的信息都更新了
}
void delnode(int rt) //为了回收空间,其实没什么太大的用处
{
	f[rt] = val[rt] = sz[rt] = mi[rt] = 0;
	ch[rt][0] = ch[rt][1] = rev[rt] = lazy[rt] = 0;
}
void pushup(int x)  //跟线段树一样,从下往上不断更新
{
	if (!x)return;
	sz[x] = 1, mi[x] = val[x];
	if (ch[x][0]) sz[x] += sz[ch[x][0]], mi[x] = Min(mi[x], mi[ch[x][0]]); //更新个数跟当前子树最小值
	if (ch[x][1]) sz[x] += sz[ch[x][1]], mi[x] = Min(mi[x], mi[ch[x][1]]);
}
void pushdown(int x) //向下传递lazy 跟 rev
{
	if (!x) return;
	if (lazy[x])
	{
		update_add(ch[x][0], lazy[x]);
		update_add(ch[x][1], lazy[x]);
		lazy[x] = 0;
	}
	if (rev[x])
	{
		update_rev(ch[x][0]);
		update_rev(ch[x][1]);
		rev[x] = 0;
	}
}
void build(int &rt, int l, int r, int fa) //rt是节点编号,节点的大小代表了两个数位置的相对顺序
{                       //一共tot个节点
	if (l > r) return;
	int mid = (r + l) >> 1;
	rt = mid; newnode(rt, val[rt], fa);
	build(ch[rt][0], l, mid - 1, rt);
	build(ch[rt][1], mid + 1, r, rt);
	pushup(rt);
}
void Rotate(int x, int k) // k = 0左旋, k = 1右旋
{
	int y = f[x]; int z = f[y];
	pushdown(y); pushdown(x);
	ch[y][!k] = ch[x][k];
	if (ch[x][k]) f[ch[x][k]] = y;
	f[x] = z;
	if (z) ch[z][ch[z][1] == y] = x;
	f[y] = x; ch[x][k] = y;
	pushup(y), pushup(x);
}
void splay(int x, int goal)
{
	pushdown(x);
	while (f[x] != goal)
	{
		int y = f[x], z = f[y];
		//在这里下传翻转标记,在rotate里下传标记可能会使树形改变导致旋转出错
		pushdown(z); pushdown(y); pushdown(x);
		if (f[y] == goal) Rotate(x, ch[y][0] == x);
		else
		{
			int p = ch[f[y]][0] == y;
			if (ch[y][p] == x) Rotate(x, !p), Rotate(x, p);
			else Rotate(y, p), Rotate(x, p);
		}
	}
	pushup(x);
	if (goal == 0) root = x;
}

//以x为根的子树 的极值点  0 极小 1 极大
int extreme(int x, int k)
{
	while (ch[x][k]) x = ch[x][k];
	splay(x, 0);  //所有操作之后都伸展下
	return x;
}
//以节点编号x为根的子树 第k个数的节点编号
int kth(int x, int k)
{
	pushdown(x);
	if (sz[ch[x][0]] + 1 == k) return x;
	else if (sz[ch[x][0]] >= k) return kth(ch[x][0], k);
	else return kth(ch[x][1], k - sz[ch[x][0]] - 1);
}
//区间交换
void exchange(int l1, int r1, int l2, int r2)
{
	int x = kth(root, l2 - 1), y = kth(root, r2 + 1);
	splay(x, 0), splay(y, x);
	int tmp_right = ch[y][0]; ch[y][0] = 0; //“剪贴下来”
	x = kth(root, l1 - 1), y = kth(root, l1);
	splay(x, 0), splay(y, x);
	ch[y][0] = tmp_right;
	f[tmp_right] = y;
}
//区间翻转
void reversal(int l, int r)
{
	int x = kth(root, l - 1), y = kth(root, r + 1);
	splay(x, 0); splay(y, x);
	update_rev(ch[y][0]);  //ch[y][0]就是l-r区间
}
//区间加
void add(int l, int r, int v)
{
	int x = kth(root, l - 1), y = kth(root, r + 1);
	//    cout << 1 <<endl;
	splay(x, 0); splay(y, x);
	update_add(ch[y][0], v); //ch[y][0]就是l-r区间
}
//在第k个数后插入值为x的节点
void Insert(int k, int x) {
	int r = kth(root, k), rr = kth(root, k + 1);
	splay(r, 0), splay(rr, r);
	newnode(++tot, x, rr); ch[rr][0] = tot; //节点个数增加
	for (r = rr; r; r = f[r]) pushdown(r), pushup(r);
	splay(rr, 0);
}
//删除第k位置的数
void Delete(int k)
{
	splay(kth(root, k - 1), 0);
	splay(kth(root, k + 1), root);
	delnode(ch[ch[root][1]][0]);
	ch[ch[root][1]][0] = 0;
	pushup(ch[root][1]);
	pushup(root);
}
// 获取区间最大值
//int get_max(int l,int r)
//{
//    int x = kth(root,l-1), y = kth(root,r+1);
//    splay(x,0); splay(y,x);
//    return mx[ch[y][0]];
//}
//获取区间最小值
int get_min(int l, int r)
{
	int x = kth(root, l - 1), y = kth(root, r + 1);
	splay(x, 0); splay(y, x);
	return mi[ch[y][0]];
}
void init(int n)
{
	root = 0;
	//不断更新的, 不断插入的, 需要一个tot记录插入节点的编号
//    tot = 0;
//    newnode(++tot, -INF, 0);
//    newnode(++tot, INF, root);
//    ch[root][1] = tot;
	f[0] = sz[0] = ch[0][0] = ch[0][1] = rev[0] = lazy[0] = 0; //rt编号多加两个,处理区间[1,n]
	build(root, 1, n, 0);
	pushup(root);
}
char s[12];
int main()
{
	scanf("%d", &n);
	val[1] = val[n + 2] = INF; //多加两个编号0,n+1, 把区间1-n包起来
	for (int i = 2; i <= n + 1; i++) scanf("%d", &val[i]);
	tot = n + 2;
	init(n + 2);
	scanf("%d", &m);
	for (int i = 1; i <= m; i++) {
		int d, l, r;
		scanf(" %s", s);
		if (s[0] == 'A')
		{ //ADD
			scanf("%d%d%d", &l, &r, &d);
			add(l + 1, r + 1, d);
		}
		else if (s[0] == 'I')
		{ //INSERT
			scanf("%d%d", &l, &d);
			Insert(l + 1, d);
		}
		else if (s[0] == 'M')
		{ //MIN
			scanf("%d%d", &l, &r);
			printf("%d\n", get_min(l + 1, r + 1));
		}
		else if (s[0] == 'D')
		{ //DELETE
			scanf("%d", &l);
			Delete(l + 1);
		}
		else if (s[3] == 'E')
		{ //REVERSE
			scanf("%d%d", &l, &r);
			reversal(l + 1, r + 1); //增加了1一个节点全体后移一个
		}
		else
		{ //REVOLVE
			scanf("%d%d%d", &l, &r, &d);
			d = d % (r - l + 1);
			if (d) exchange(l + 1, r - d + 1, r - d + 1 + 1, r + 1);
		}
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值