关于Splay的几个灵魂问题

关于Splay的几个灵魂问题

写着一篇文章的目的在于,我被几道带有lazy标记的题折磨了很久。

1. Splay是在维护节点值的顺序吗?

我们先来看一看常规的splay定义:

const int N = 1e5+5;

struct Node{
  int s[2]; 	// 左右儿子
  int p;			// 父节点
  int v;			// 节点值
  int size;		// 子树大小
}tr[N];

请问,splay是在维护节点的信息v吗?要回答这个问题,我们应该看看影响splay平衡性的两个rotate,splay

int ws(int x){
  return tr[tr[x].p].s[1] == x;
}

void push_up(int x){
	tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;
}

void rotate(int x){
  int y = tr[x].p;
  int z = tr[y].p;
  int k = ws(x);
	tr[z].s[ws(y)] = x;
  tr[y].p = x;
  tr[y].s[k] = tr[x].s[k^1];
  tr[tr[x].s[k^1]].p = y;
  tr[x].p = z;
  tr[x].s[k^1] = y;
  push_up(y);
  push_up(x);
}

void splay(int x, int k){
  while(tr[x].p != k){
		int y = tr[x].p;
    int z = tr[y].p;
    if(z != k){
      if(ws(x) ^ ws(y)){
        rotate(x);
      }else{
        rotate(y);
      }
    }
    rotate(x);
  }
  if(!k) root = x;
}

可以看出,splay在旋转时并不会像其他平衡树(如AVL)一样对节点值进行比较。事实上,splay并不会维护节点值的大小顺序,而是在维护整棵树的中序遍历不变。

2. Splay的lazy什么时候需要push_down?

push_down会影响该节点的子树的信息。一方面是在需要访问子树信息时push_down,这个自然不必多提;另一方面,如果push_down操作会改变树的结构,如交换左右子树,而查询操作要求获得某一侧子树的大小,或判断该节点是哪一个儿子(这个很常见),则必须先push_down

我们以flip_lazy为例,给出两种风格的lazy,请选择合适的一种使用:

  • push_down不改变本节点信息

    void push_down(int x){
        if(tr[x].lazy){
            if(tr[x].s[0]){
                swap(tr[tr[x].s[0]].s[0], tr[tr[x].s[0]].s[1]);
                tr[tr[x].s[0]].lazy ^= 1;
            }
            if(tr[x].s[1]){
                swap(tr[tr[x].s[1]].s[0], tr[tr[x].s[1]].s[1]);
                tr[tr[x].s[1]].lazy ^= 1;
            }
            tr[x].lazy = 0;
        }
    }
    
    void set_lazy(int x){
      	tr[x].lazy ^= 1;
      	swap(tr[x].s[0], tr[x].s[1]);
    }
    
  • push_down改变本节点信息

    void push_down(int x){
        if(tr[x].lazy){
            swap(tr[x].s[0], tr[x].s[1]);
            if(tr[x].s[0]) tr[tr[x].s[0]].lazy ^= 1;
            if(tr[x].s[1]) tr[tr[x].s[1]].lazy ^= 1;
            tr[x].lazy = 0;
        }
    }
    
    void set_lazy(int x){
    		tr[x].lazy ^= 1;
    }
    

此外,rotatesplaygetkget_rank操作都需要push_down

void rotate(int x){
    int y = tr[x].p;
    int z = tr[y].p;
    

    push_down(y);
    push_down(x);
    
    int k = ws(x);
    
    tr[z].s[ws(y)] = x;
    tr[y].p = x;
    tr[y].s[k] = tr[x].s[k^1];
    tr[tr[x].s[k^1]].p = y;
    tr[x].p = z;
    tr[x].s[k^1] = y;
    push_up(y);
    push_up(x);
}

void splay(int x, int k){
    while (tr[x].p != k) {
        int y = tr[x].p;
        int z = tr[y].p;
        push_down(z);
        push_down(y);
        push_down(x);
        if(z != k){
            if(ws(x) ^ ws(y)){
                rotate(x);
            }else{
                rotate(y);
            }
        }
        rotate(x);
    }
    if(!k) rt = x;
}

int build(int l, int r, int p){
    if(l > r) return 0;
    else if(l == r){
        tr[l].lazy = 0;
        tr[l].p = p;
        tr[l].v = arr[l];
        tr[l].s[0] = tr[l].s[1] = 0;
        tr[l].size = 1;
        return l;
    }else{
        int mid = (l+r)>>1;
        tr[mid].lazy = 0;
        tr[mid].p = p;
        tr[mid].v = arr[mid];
        tr[mid].s[0] = build(l, mid-1, mid);
        tr[mid].s[1] = build(mid+1, r, mid);
        
        push_up(mid);
        return mid;
    }
}

int getk(int k){
    int u = rt;
    while(u){
        push_down(u);
        if(tr[tr[u].s[0]].size >= k){
            u = tr[u].s[0];
        }else if(tr[tr[u].s[0]].size + 1 == k){
            return u;
        }else{
            k -= tr[tr[u].s[0]].size + 1;
            u = tr[u].s[1];
        }
    }
    return -1;
}

int get_rank(int u){
    splay(u, 0);
    push_down(u);
    return tr[tr[u].s[0]].size;
}
posted @ 2021-03-13 10:22  popozyl  阅读(119)  评论(0编辑  收藏  举报