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

【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   
#include   
#include   
#include   
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<' ';
    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;
}

你可能感兴趣的:(BZOJ,splay,BZOJ,splay,区间翻转)