P3391文艺平衡树题解

· · 题解

洛谷P3391

我又来啦!
第一眼:哎!平衡树。
所以这次我们还是可以用无旋 Treap 来做。
和上次不一样,这道题增加了区间操作。 (而已)
对于普通平衡树(P3369)还没 AC 的朋友,可以先看这篇题解。

解法说明

一、不同点:

  1. 这道题涉及到子树操作,所以要 pushdown。
    void pushdown(int x) { // 下传懒标记
    if (node[x].tag) {
        node[node[x].l].tag ^= 1; // 一定要取反,否则后果自行脑补……
        node[node[x].r].tag ^= 1;
        node[x].tag = 0; // 取消
    }
    }
  2. 这道题的区间翻转,要用到排名来进行分裂与合并。
    
    void split(int u, int & l, int & r, int k) { // 分裂
    if (!u) { // 分到底了
        l = r = 0;
        return;
    }
    if (node[u].tag) { // 有懒标记才下传
        pushdown(u);
    }
    int ln = node[node[u].l].sz; // 先取k个
    if (ln < k) {
        l = u;
        split(node[u].r, node[l].r, r, k - ln - 1);
    } else {
        r = u;
        split(node[u].l, l, node[r].l, k);
    }
    pushup(u); // 上传
    }

int merge(int l, int r) { // 合并 if (!l || !r) { return l + r; } if (node[l].pty <= node[r].pty) { // 按优先值合并 if (node[l].tag) pushdown(l); // 下传 node[l].r = merge(node[l].r, r); pushup(l); // 上传 return l; } else { if (node[r].tag) pushdown(r); node[r].l = merge(l, node[r].l); pushup(r); return r; } }

之后通过翻转就可以了。  
### 预处理
来想想,如何取出 $[l,r]$?  
利用差分的思想。  
先从集合中取出 $[root,r]$。  
再从 $[root,r]$ 中取出 $[root,l-1]$。  
这样我们就得到了 $[l,r]$。  
```cpp
void reverse(int tl, int tr) {
    int p1, p2, p3, p4;
    p1 = p2 = p3 = p4 = 0;
    split(root, p1, p2, tr); // 取出[root,r]
    split(p1, p3, p4, tl - 1); // 再取出[root,l-1]
    node[p4].tag ^= 1; // 打标记
    p1 = merge(p3, p4); // 合并(注意先后顺序,从后往前)
    root = merge(p1, p2);
}

二、区间翻转
实质上,区间翻转就是将子树进行交换。
WHY?
因为平衡树等于 BST 和堆。
Then?
它维护的是中序遍历序列。
So……

void pushdown(int x) {
    if (node[x].tag) {
        node[node[x].l].tag ^= 1;
        node[node[x].r].tag ^= 1;
        swap(node[x].l, node[x].r); // 交换子树
        node[x].tag = 0;
    }
}

三、输出结果
输出因为 Treap 里存的是中序遍历,所以按中序遍历输出即可。

void print(int x) {
    if (!x) { // 到底了
        return;
    }
    if (node[x].tag) { // 注意要再检查一遍有没有遗漏的懒标记,有的话就下传
        pushdown(x);
    }
    print(node[x].l); // 左
    cout << node[x].val << " "; // 根
    print(node[x].r); // 右
}

四、完整代码,无注释:

#include <bits/stdc++.h>

using namespace std;

const int N = 100000 + 5;

struct tree {
    int l, r, pty, tag, sz, val;
} node[N];

int dl, dr, n, m, root, num;

void pushup(int x) {
    node[x].sz = node[node[x].l].sz + node[node[x].r].sz + 1;
}

void pushdown(int x) {
    if (node[x].tag) {
        node[node[x].l].tag ^= 1;
        node[node[x].r].tag ^= 1;
        swap(node[x].l, node[x].r);
        node[x].tag = 0;
    }
}

void split(int u, int & l, int & r, int k) {
    if (!u) {
        l = r = 0;
        return;
    }
    if (node[u].tag) {
        pushdown(u);
    }
    int ln = node[node[u].l].sz;
    if (ln < k) {
        l = u;
        split(node[u].r, node[l].r, r, k - ln - 1);
    } else {
        r = u;
        split(node[u].l, l, node[r].l, k);
    }
    pushup(u);
}

int merge(int l, int r) {
    if (!l || !r) {
        return l + r;
    }
    if (node[l].pty <= node[r].pty) {
        if (node[l].tag) pushdown(l);
        node[l].r = merge(node[l].r, r);
        pushup(l);
        return l;
    } else {
        if (node[r].tag) pushdown(r);
        node[r].l = merge(l, node[r].l);
        pushup(r);
        return r;
    }
}

void add(int val) {
    num++;
    node[num].val = val;
    node[num].sz = 1;
    node[num].pty = rand();
}

void reverse(int tl, int tr) {
    int p1, p2, p3, p4;
    p1 = p2 = p3 = p4 = 0;
    split(root, p1, p2, tr);
    split(p1, p3, p4, tl - 1);
    node[p4].tag ^= 1;
    p1 = merge(p3, p4);
    root = merge(p1, p2);
}

void ins(int val) {
    split(root, dl, dr, val - 1);
    add(val);
    root = merge(merge(dl, num), dr);
}

void print(int x) {
    if (!x) {
        return;
    }
    if (node[x].tag) {
        pushdown(x);
    }
    print(node[x].l);
    cout << node[x].val << " ";
    print(node[x].r);
}

int main(void) {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        ins(i);
    }
    for (int i = 1; i <= m; i++) {
        int x, y;
        cin >> x >> y;
        reverse(x, y);
    }
    print(root);
    return 0;
}

完结撒花~~~

至于代码讲解不清楚或错误等问题,欢迎在评论区提出~