「Bzoj 4552」「HEOI2016/TJOI2016」排序 (线段树合并分裂 + Set / 二分 + 线段树)

BZOJ 4552
题意:给出一个$1$到$n$的全排列,现在对这个全排列序列进行$m$次局部排序,排序分为两种:
$1:(0,l,r)$表示将区间$[l,r]$的数字升序排序
$2:(1,l,r)$表示将区间$[l,r]$的数字降序排序最后询问第$q$位置上的数字。

方法一
只有一个位置,我们可以二分这个上面的数是多少。
考虑将数的排序转化为二进制数$01$的排序。因为二进制数$01$的排序只需要升序将所有$0$放在$1$前,降序反之。
那么我们怎么将数转化成这个呢,我们考虑大于二分值的数赋值为$1$, 否则为$0$,对询问离线,线段树辅助$01$数排序,求出最后的结果,然后如果$q$上是$1$,说明当前二分值小了,否则大了。
线段树排序$01$数即查询区间$01$个数,再区间修改
本方法难想但是代码好写。

方法二
考虑直接模拟题目的过程,我们可以将原来的每个数变成孤立的每个点,那么每次排序都会合并一系列点,并且此时还可能需要分裂。我们考虑一个支持合并、按前$k$大分裂的权值线段树。用一个$\text{Set}(l,r,nd,op)$来维护区间合并情况,即记录$[l,r]$的根在$nd$, 是升序还是降序,然后分裂时二分找出来后处理即可。具体可以看代码实现。这里体现了数据结构互相辅助的优势。
本方法好想,但是代码难写。本题为了训练线段树分裂,我写了方法二的代码。

本文借鉴于 HEOI2016/TJOI2016 排序 解题报告(二分答案/线段树分裂合并+set) - 星星之火OIer - 博客园

知识点
1、将数的排序转化为二进制数$01$的排序的方法 (用于二分)。
2、数据结构互相辅助的优势。

#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;

namespace flyinthesky {

    const int MAXN = 100000 + 5;

    int n, m, a[MAXN];

    struct data { // set 记录节点
        int l, r, nd, op; // 区间,根,是降序还是升序
        bool operator < (const data &rhs) const {return r == rhs.r ? l < rhs.l : r < rhs.r;}
    };

    namespace seg {
        #define M ((l + r) >> 1)
        int sz, lc[MAXN * 60], rc[MAXN * 60], sumv[MAXN * 60];
        void update(int l, int r, int &now, int x) { // 加一个值
            if (!now) now = ++sz;
            ++sumv[now];
            if (l == r) return ;
            if (x <= M) update(l, M, lc[now], x); else update(M + 1, r, rc[now], x);
        }
        int kth(int l, int r, int now, int k) { // 找 k 大
            if (l == r) return l;
            int whw = sumv[lc[now]];
            if (k <= whw) return kth(l, M, lc[now], k);
            else return kth(M + 1, r, rc[now], k - whw);
        }
        void split(int l, int r, int now, int &res, int k) { // 按 k 大将小于等于 k 的分到 res 里,其他留在 now 里
            if (!now) return ;
            if (!res) res = ++sz;
            if (l == r) {sumv[now] -= k, sumv[res] += k; return ;}
            int whw = sumv[lc[now]];
            if (whw > k) split(l, M, lc[now], lc[res], k);
            else if (whw == k) lc[res] = lc[now], lc[now] = 0;
            else if (whw < k) {
                lc[res] = lc[now], lc[now] = 0;
                split(M + 1, r, rc[now], rc[res], k - whw);
            } // 和 kth 差不多
            sumv[now] = sumv[lc[now]] + sumv[rc[now]];
            sumv[res] = sumv[lc[res]] + sumv[rc[res]]; // pushup
        } 
        int merge(int &x, int y) { // 合并两个线段树
            if (x == 0) return x = y;
            if (y == 0) return 0;
            sumv[x] += sumv[y];
            return merge(lc[x], lc[y]), merge(rc[x], rc[y]);
        }
    }
    namespace S {
        set<data > s;
        int split(int l, int r) { // 将 [l, r] 分出来使得 set 里有这个区间,返回这个区间的根
            set<data >::iterator it = s.lower_bound((data){0, l, 0, 0}); 
            if (it->l != l) { // l 在 *it 区间里
                data whw = *it; s.erase(it); int gg = 0;
                if (whw.op == 1) { // 降序 
                    seg::split(1, n, whw.nd, gg, whw.r - l + 1); 
                    s.insert((data){whw.l, l - 1, whw.nd, 1});
                    s.insert((data){l, whw.r, gg, 1});
                } else { // 增序
                    seg::split(1, n, whw.nd, gg, l - whw.l);
                    s.insert((data){whw.l, l - 1, gg, 0});
                    s.insert((data){l, whw.r, whw.nd, 0});
                } 
            }
            it = s.lower_bound((data){0, r, 0, 0});
            if (it->r != r) { // r 在 *it 区间里
                data whw = *it; s.erase(it); int gg = 0;
                if (whw.op == 1) { // 降序 
                    seg::split(1, n, whw.nd, gg, whw.r - r);
                    s.insert((data){whw.l, r, whw.nd, 1});
                    s.insert((data){r + 1, whw.r, gg, 1});
                } else { // 增序 
                    seg::split(1, n, whw.nd, gg, r - whw.l + 1);
                    s.insert((data){whw.l, r, gg, 0});
                    s.insert((data){r + 1, whw.r, whw.nd, 0});
                } 
            }
            int ret = 0;
            while (1) { // 将刚刚分出来的区间合成一个
                it = s.lower_bound((data){0, l, 0, 0});
                if (it == s.end() || it->r > r) break ;  //
                seg::merge(ret, it->nd);
                s.erase(it);
            }
            return ret;
        }
    }

    void clean() {
    }
    int solve() {

        clean();
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            int x = 0;
            seg::update(1, n, x, a[i]);
            S::s.insert((data){i, i, x, 0});
        }
        for (int op, l, r, i = 1; i <= m; ++i) {
            scanf("%d%d%d", &op, &l, &r);
            int x = S::split(l, r);
            S::s.insert((data){l, r, x, op}); // 记得将整个区间加到 set 里
        }
        int p; scanf("%d", &p);
        int x = S::split(p, p);
        printf("%d\n", seg::kth(1, n, x, 1));

        return 0;
    } 
}
int main() {
    flyinthesky::solve();
    return 0;
}
------ 本文结束 ------