【线段树合并】【P2824】 [HEOI2016/TJOI2016]排序

Description

给定一个长度为 \(n\) 的排列,有 \(m\) 次操作,每次选取一段局部进行升序或降序排序,问你一波操作后某个位置上的数字是几

Hint

\(1~\leq~n,~m~\leq~10^5\)

Solution

有两种做法,一种在线一种离线,这里把在线部分讲得更清楚点吧……

考虑离线算法,我们二分该位置上的答案,将大于该数的元素置为 \(1\),小于该数的元素置为 \(0\),然后模拟所有的排序并检验。由于使用线段树对 \(0/1\) 序列多次局部排序可以做到 \(O(m~\log n~+~n)\) 的复杂度,所以总复杂度为 \(O(m~\log^2 n)\)。

具体排序的做法为使用线段树维护当前区间有多少个 \(1\),不妨设为 \(x\) 个。如果对该区间升序排序,则将后面 \(x\) 个数置为 \(1\),剩下的置为 \(0\),否则将前面 \(x\) 个数置为 \(1\),其余置为 \(0\)。于是一次操作的复杂度为 \(O(\log n)\),于是进行 \(m\) 次操作的复杂度为 \(O(m~\log n)\)。

考虑在线做。发现对于任意的时刻我们都有一些区间是排好序的。那么现在我们的问题是每次排序操作后合并被排序操作覆盖的区间。考虑到对于两个序列的按序合并可以使用权值线段树轻松做到,我们对每个排好序的区间分别维护一棵权值线段树。然后用一个 set 维护这些区间。对于被该排序操作完全覆盖的区间,我们可以直接进行线段树合并,而对于区间两侧的被覆盖了一部分的两个区间,可以先分裂成被完全覆盖的区间和完全不被覆盖的区间再进行合并。例如被排序的区间是 \([l,~r]\),左侧被覆盖了一部分的区间为 \([l_0,~y_0]\),其中 \(l_0~<~l~<~r_0\),那么将区间 \([l_0,~r_0]\) 拆分成 \([l_0,~l - 1]~\bigcap~[l,~r_0]\) 两个区间,然后对 \([l,~r_0]\) 与其他被完全覆盖的区间进行合并即可。

考虑复杂度分析:

如果不考虑线段树分裂操作,我们对区间的操作实质上是将很多的小区间合并成至少一个大区间。考虑到对 \(n\) 个长度为 \(1\) 的小区间全部合并成一个大区间的复杂度为 \(O(n~\log n)\),于是 merge 部分的的总复杂度为 \(O(n~\log n)\)。考虑分裂,一次排序操作后会分裂出 \(O(1)\) 个新区间,于是 \(m\) 次操作后会分裂出 \(O(m)\) 个新区间,合并这 \(O(m)\) 个区间的复杂度仍为 \(O(m~\log n)\),考虑到每次分裂是严格 \(O(\log n)\) 的,且会进行 \(O(m)\) 次排序,所以分裂操作对复杂度的总贡献仍是 \(O(m~\log n)\)。于是总复杂度为 \(O((n+m)~\log n)\)。比上面的离线算法更优秀。同时这个算法可以解决查询任意多个位置上的数字,而不是仅仅一个。

Code

在分裂区间的时候有很多小细节需要注意……另外下面的代码存在一定的内存泄漏问题,不过总共被泄露的内存是 \(O((n+m)~\log n)\) 级别的,对空间复杂度不产生影响,是可以接受的。

其实是我不知道为什么回收空间会莫名其妙 RE

#include <cstdio>
#include <set>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
    const int L = 1000000;
    char buf[L], *front=buf, *end=buf;
    char GetChar() {
        if (front == end) {
            end = buf + fread(front = buf, 1, L, stdin);
            if (front == end) return -1;
        }
        return *(front++);
    }
}

template <typename T>
inline void qr(T &x) {
    char ch = IPT::GetChar(), lst = ' ';
    while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
    while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
    if (lst == '-') x = -x;
}

namespace OPT {
    char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
    if (x < 0) {x = -x, putchar('-');}
    int top=0;
    do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
    while (top) putchar(OPT::buf[top--]);
    if (pt) putchar(aft);
}

const int maxn = 100010;

struct Tree {
    Tree *ls, *rs;
    int l, r, v;

    Tree() {
        ls = rs = NULL;
        l = r = v = 0;
    }

    inline void pushup() {if (this->l != this->r) this->v = (this->ls ? this->ls->v : 0) + (this->rs ? this->rs->v : 0); else this->v = 1;}
};

struct OP {
    int l, r;
    bool up;
    Tree *rot;

    inline bool operator<(const OP &_others) const {
        return this->r < _others.r;
    }

    OP(int _l = 0, int _r = 0, bool _up = 0, Tree *_rot = 0) {
        l = _l; r = _r; up = _up; rot = _rot;
    }
};
OP temp;
std::set<OP>s;

int n, m;
int MU[maxn];

void split(int, bool);
void insert(Tree*, int, int, int);
void split(Tree*, Tree*, Tree*, int);
Tree* merge(Tree*, Tree*);
int query(Tree*, int);

int main() {
    freopen("data.in", "r", stdin);
    freopen("my.out", "w", stdout);
    qr(n); qr(m);
    for (int i = 1; i <= n; ++i) {
        auto _rot = new Tree; qr(MU[i]);
        insert(_rot, 1, n, MU[i]); s.insert(OP(i, i, true, _rot));
    }
    for (int j = 1, a, b, c; j <= m; ++j) {
        a = b = c = 0; qr(a); qr(b); qr(c);
        split(b, true); split(c + 1, false);
        auto l = s.lower_bound({0, b, true, NULL}), r = s.lower_bound({0, c + 1, true, NULL});
        auto _tmp = *l;
        for (auto i = s.erase(l); i != r; i = s.erase(i)) {
            _tmp.rot = merge(_tmp.rot, (*i).rot);
        }
        _tmp.l = b; _tmp.r = c; _tmp.up = !a;
        s.insert(_tmp);
    }
    int q = 0; qr(q);
    auto _ans = s.lower_bound({0, q, true, NULL}); auto ans = *_ans;
    int k = ans.up ? q - ans.l + 1 : ans.r - q + 1;
    qw(query(ans.rot, k), '\n', true);
    return 0;
}

int query(Tree *u, int k) {
    if (u->l == u->r) return u->l;
    if (!u->ls) return query(u->rs, k);
    if (u->ls->v >= k) return query(u->ls, k);
    return query(u->rs, k - u->ls->v);
}

Tree* merge(Tree *u, Tree *v) {
    if (!u) return v; else if (!v) return u;
    u->v += v->v;
    u->ls = merge(u->ls, v->ls);
    u->rs = merge(u->rs, v->rs);
    return u;
}

void insert(Tree *u, int l, int r, int v) {
    ++u->v;
    if ((u->l = l) == (u->r = r)) return;
    int mid = (l + r) >> 1;
    if (v <= mid) insert(u->ls = new Tree, l, mid, v);
    else insert(u->rs = new Tree, mid + 1, r, v);
}

void split(int x, bool isfront) {
    auto k = s.lower_bound({0, x, true, NULL}); if (k == s.end()) return; auto t = *k;
    if (t.l == x) return;
    s.erase(k);
    int _k = (isfront ? t.r - x + 1: x - t.l), len = t.r - t.l + 1;
    if (t.up == isfront) {
        Tree *_rot = new Tree;
        _k = len - _k;
        if (!_k) {
            s.insert(t); return;
        }
        split(t.rot, t.rot, _rot, _k);
        if (!t.up) {
            _k = len - _k; std::swap(_rot, t.rot);
        }
        s.insert({t.l, t.l + _k - 1, t.up, t.rot});
        s.insert({t.l + _k, t.r, t.up, _rot});
    } else {
        Tree *_rot = new Tree;
        if (!_k) {
            s.insert(t); return;
        }
        split(t.rot, t.rot, _rot, _k);
        if (!t.up) {
            std::swap(t.rot, _rot); _k = len -_k;
        }
        s.insert({t.l, t.l + _k - 1, t.up, t.rot});
        s.insert({t.l + _k, t.r, t.up, _rot});
    }
}

void split(Tree *u, Tree *l, Tree *r, int k) {
    l->l = r->l = u->l; r->r = l->r = u->r;
    if (!u->ls) split(u->rs, l->rs ? l->rs : l->rs = new Tree, r->rs ? r->rs : r->rs = new Tree, k);
    else if (k == u->ls->v) {
        l->ls = u->ls; r->rs = u->rs; l->rs = NULL; r->ls = NULL;
    } else if (k < u->ls->v) {
        split(u->ls, l->ls ? l->ls : l->ls = new Tree, r->ls ? r->ls : r->ls = new Tree, k);
        r->rs = u->rs; l->rs = NULL;
    } else {
        split(u->rs, l->rs ? l->rs : l->rs = new Tree, r->rs ? r->rs : r->rs = new Tree, k - u->ls->v);
    l->ls = u->ls; r->ls = NULL;
    }
    l->pushup(); r->pushup();
}

Summary

1、将 \(n\) 个长度为 \(1\) 的小区间合并为一个大区间的复杂度为 \(O(n~\log n)\),理由是这样的开销显然不大于将这些区间顺次插入一棵线段树。

2、合并两个有序序列可以使用合并权值线段树来做到均摊复杂度 \(O(\log n)\)。

原文地址:https://www.cnblogs.com/yifusuyi/p/10438005.html

时间: 2024-10-28 10:14:13

【线段树合并】【P2824】 [HEOI2016/TJOI2016]排序的相关文章

Luogu P2824 [HEOI2016/TJOI2016]排序

Link 先二分答案,这样所有的数字就都变成了\(0,1\). 那么区间排序就相当于区间求和再区间覆盖了. #include<bits/stdc++.h> using namespace std; #define N 100007 int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;} int a[N],sum[N<&l

[HEOI2016&amp;TJOI2016] 排序(线段树)

4552: [Tjoi2016&Heoi2016]排序 Time Limit: 60 Sec  Memory Limit: 256 MBSubmit: 2703  Solved: 1386[Submit][Status][Discuss] Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排 序分为两种:1:(0,l,

[HEOI2016/TJOI2016]排序 解题报告

[HEOI2016/TJOI2016]排序 题意 给出一个大小为 \(n\) 的排列, 对这个排列进行 \(m\) 次操作, 操作分为以下两种, 0 l r 表示将区间 \([l,r]\) 的数升序排序. 1 l r 表示将区间 \([l,r]\) 的数降序排序. 询问 \(m\) 次操作后下标为 \(q\) 的数字. 思路 不看题解打死也想不出来系列 考虑二分答案. 设当前二分的答案为 \(mid\), 把原排列中 大于等于 \(mid\) 的数标记为 \(1\), 小于 \(mid\) 的数

[BZOJ3545] [ONTAK2010]Peaks(线段树合并 + 离散化)

传送门 由于困难值小于等于x这个很恶心,可以离线处理,将边权,和询问时的x排序. 每到一个询问的时候,将边权小于等于x的都合并起来再询问. .. 有重复元素的线段树合并的时间复杂度是nlog^2n #include <cstdio> #include <iostream> #include <algorithm> #define N 500001 int n, m, q, cnt, tot, size; int sum[N * 10], ls[N * 10], rs[N

【bzoj3545】[ONTAK2010]Peaks 线段树合并

[bzoj3545][ONTAK2010]Peaks 2014年8月26日3,1512 Description 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1. Input 第一行三个数N,M,Q.第二行N个数,第i个数为h_i接下来M行,每行3个数a b c,表示从a到b有一条困难

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

HDU 5649 DZY Loves Sorting(二分答案+线段树、线段树合并+线段树分割)

题意 一个 \(1\) 到 \(n\) 的全排列,\(m\) 种操作,每次将一段区间 \([l,r]\) 按升序或降序排列,求 \(m\) 次操作后的第 \(k\) 位. \(1 \leq n \leq 10^5\) 思路 两个 \(\log\) 的做法展现了二分答案的强大功能.首先二分枚举第 \(k\) 位的值,然后将小于等于它的数都变为 \(1\) ,大于它的数变为 \(0\) ,线段树可以实现对 \(01\) 序列快速的排序,按要求进行排序,然后如果第 \(k\) 位为 \(1\) 说明这

Codeforces.1051G.Distinctification(线段树合并 并查集)

题目链接 \(Description\) 给定\(n\)个数对\(A_i,B_i\).你可以进行任意次以下两种操作: 选择一个位置\(i\),令\(A_i=A_i+1\),花费\(B_i\).必须存在一个位置\(j\),满足\(A_i=A_j,\ i\neq j\),才可以进行. 选择一个位置\(i\),令\(A_i=A_i-1\),花费\(-B_i\).必须存在一个位置\(j\),满足\(A_i=A_j+1\),才可以进行. 你需要对于所有\(i\in[1,n]\),求使得\(A_1,A_2,

[树上差分][线段树合并]JZOJ 3397 雨天的尾巴

Description 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连 根拔起,以及田地里的粮食被弄得一片狼藉. 无奈的深绘里和村民们只好等待救济粮来维生. 不过救济粮的发放方式很特别. 首先村落里的一共有n 座房屋,并形成一个树状结构.然后救济粮分m 次发放,每次选择 两个房屋(x,y),然后对于x 到y 的路径上(含x 和y) 每座房子里发放一袋z 类型的救济粮.