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

来源:互联网 发布:网络访问安全设计 编辑:程序博客网 时间:2024/05/29 08:53

【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;}
阅读全文
0 0
原创粉丝点击