您的位置:首页 > 其它

区间翻转 bzoj 3223 文艺平衡树 (splay)

2017-05-26 17:20 323 查看

【bzoj3223】Tyvj 1729 文艺平衡树

Description

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

Input

第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n) m表示翻转操作次数 接下来m行每行两个数[l,r] 数据保证 1<=l<=r<=n

Output

输出一行n个数字,表示原始序列经过m次变换后的结果

Sample Input

5 3

1 3

1 3

1 4

Sample Output

4 3 2 1 5

HINT

N,M<=100000

思路

区间翻转问题

翻转区间->交换BST的左右子树(逐层交换,正确性请自行验证),区间标记降低翻转次数

寻找要操作的区间[l,r]:将当前排名(size)为l的节点(节点l-1)转到根,将当前排名为r+2的节点(节点r+1)转到根的右子树的根节点,则根的右子树的根节点的左子树为所求区间,直接标记该区间就可以了。(类似线段树区间修改)

splay操作维护一棵较优的树,三种方式进行旋转操作。

注意

1.标记是在每一次访问到一个新的节点是就要pushdown的(改变树的结构会破坏标记区间,所以先一步下传标记)

2.区分一个节点的排名和这个节点的值:这个节点的排名是它是当前数组中的第几个,用左儿子的size+1表示;这个节点的值是题目中输入的数字,在本题中是1~n

3.增加数字为0和n+1的两个哨兵节点,因为如果对区间1~x或x~n操作,用到前后的节点就需要0和n+1。

4.有些读者会疑惑,难道交换左右子树不会破坏BST的性质吗?这就是容易混淆的一点,我们的区间操作是根据下标翻转的,用数组实现时下标就是数组地址,子树交换时,只是存储内容的改变,下标位置(树的形状)只会在旋转时改变,保证BST性质。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;

const int MAXN = 100010;
const int INF = 0x7fffffff;

#define lson tr[x].ch[0]
#define rson tr[x].ch[1]

struct Node{
int ch[2], fa, v, size;
bool rv;//是否翻转,因为偶数次翻转无效,所以可以用bool,位运算
void set(int x){v=x; ch[0]=ch[1]=fa=rv=0; size=1;}
}tr[MAXN];

int n, m, root, tot, l, r, x1, x2;

void updata(int x){//维护size
if( !x ) return;
tr[x].size = 1 + tr[lson].size + tr[rson].size;
}

void pushdown(int x){//打标记必备
if( !x ) return;
if( tr[x].rv ){//x的子节点集合为翻转区间
tr[lson].rv ^= 1; tr[rson].rv ^= 1;//标记下传
tr[x].rv = 0;//消除
int t = lson; lson = rson; rson = t;//当前层翻转
}
}

inline bool son(int x) {return tr[tr[x].fa].ch[1] == x;}//判断ls,rs

void link(int x, int y, bool cc){//建立x与y的新关系
tr[x].ch[cc] = y; tr[y].fa = x;
}

void rotate(int x){//一种重新连边似的旋转方式
int fa = tr[x].fa, ffa = tr[fa].fa, tt = son(x);
link(ffa,x,son(fa)); link(fa,tr[x].ch[!tt],tt); link(x,fa,!tt);
updata(fa);
}

void splay(int x, int f){
pushdown(x);
if(f == 0) root = x;//l-1到root
while(tr[x].fa != f){//到目标位置
if (tr[tr[x].fa].fa == f) {rotate(x); break;}//离目标位置只有一步
if (son(x) == son(tr[x].fa)) {rotate(tr[x].fa); rotate(x);}//与fa形成一条链(链状是一种不优的状态,所以通过先旋fa再旋x的方式降深度)
else {rotate(x); rotate(x);}//Z字形(由于旋上去自动就会降深度,所以就是普通旋转)
}
updata(x);
}

int build(int l, int r){
if(l>r) return 0;
int x = ++tot;
int mid = (l+r) >> 1;
tr[x].set(mid);
lson = build(l, mid-1);
rson = build(mid+1, r);
tr[lson].fa = tr[rson].fa = x;
updata(x);
return x;
}

int find(int x, int y){
pushdown(x);//有标记就必须pushdown,且写成递归的形式
if (tr[lson].size+1 < y) return find(rson, y-tr[lson].size-1);
else if(tr[lson].size+1 == y) return x;
else return find(lson, y);
}

void dfs(int x){
pushdown(x);//标记在每一次访问之前都要pushdown
if(tr[x].ch[0]) dfs(tr[x].ch[0]);
if(tr[x].v<=n && tr[x].v>=1)
cout<<tr[x].v<<' ';
if(tr[x].ch[1]) dfs(tr[x].ch[1]);
}

int main(){
scanf("%d%d", &n, &m);
tot = root = 0;
tr[0].v = tr[n+1].v = INF;
root = build(0, n+1);//0和n+1哨兵节点
while(m--){
scanf("%d%d", &l, &r);
x1 = find(root, l); x2 = find(root, r+2);//找到l-1,r+1的下标(因为有一个边界0,所以第l个数其实是l-1,第r+2个数是r-1)
splay(x1,0); splay(x2,x1); updata(root);//旋转l-1到root,r+1到root->rs,标记位置为root->rs->ls此位置及其以下的内容就是l~r
tr[tr[x2].ch[0]].rv ^= 1;
updata(tr[x2].ch[0]); updata(x2); updata(root);
}
dfs(root);//中序遍历
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: