洛谷 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;
}