某巨文藝平衡樹(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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章