「TJOI / HEOI2016」排序

知识点:线段树,二分答案,01 条件转化

原题面:Loj Luogu

学线段树合并屯的题= =
用奇怪的方法过去了。

简述

给定一 \(1\sim n\) 的排列,给定 \(m\) 次修改。
每次修改给定一区间,对该区间进行升序/降序排序。
求所有修改操作完成后,一个给定位置的值。
\(1\le n,m\le 10^5\)

分析

怎么对一个 01 序列的区间排序?

显然排序后的序列一定是左侧全为 0,右侧全为 1。
可以统计区间内 0 的数量,将左侧这么长的前缀赋为 0,将右侧赋为 1。

需要支持区间查询,区间修改,线段树维护即可,单次排序复杂度 \(O(\log n)\)

考虑如何将原数列改为 01 序列。
学到了一个 强制加单调性 的套路。

考虑二分答案。
按照常规思路,二分检查 枚举值是否为答案,发现并没有单调性,并不能保证大于该值的数全 为/不为 答案。

考虑强制给他加一个单调性,二分 检查答案是否大于等于枚举值
由于检查的对象本来就就有单调性,所以可行,最后一定会收敛到答案。


以是否大于等于枚举值为条件,将原数列中大于等于枚举值的数全变为 1,小于等于枚举值的数全变为 0。
问题转化为上述 01 数列排序问题,检查最后指定位置是否为 1 即可。

单次 Check 总复杂度 \(O(m\log n)\),算法总复杂度 \(O(m\log^2 n)\)

代码

//知识点:线段树,01 条件转化 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
struct Operation {
  int op, l, r;
} op[kMaxn];
int n, m, q, ans, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  int sum1[kMaxn << 2], tag[kMaxn << 2]; //0,1,-1。 
  void Pushdown(int now_, int L_, int R_) {
    if (tag[now_] == -1) return ;
    tag[ls] = tag[now_], tag[rs] = tag[now_];
    sum1[ls] = tag[ls] * (mid - L_ + 1);
    sum1[rs] = tag[rs] * (R_ - mid);
    tag[now_] = -1;
  }
  void Pushup(int now_) {
    sum1[now_] = sum1[ls] + sum1[rs];
  }
  void Build(int now_, int L_, int R_, int lim_) {
    tag[now_] = - 1;
    sum1[now_] = 0; 
    if (L_ == R_) {
      sum1[now_] = (a[L_] >= lim_);
      return ;
    }
    Build(ls, L_, mid, lim_);
    Build(rs, mid + 1, R_, lim_);
    Pushup(now_);
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, int val_) {
    if (l_ > r_) return ;
    if (l_ <= L_ && R_ <= r_) {
      tag[now_] = val_;
      sum1[now_] = val_ * (R_ - L_ + 1);
      return ;
    }
    Pushdown(now_, L_, R_);
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
    Pushup(now_);
  }
  int Query(int now_, int L_, int R_, int l_, int r_) {
    if (l_ <= L_ && R_ <= r_) return sum1[now_];
    Pushdown(now_, L_, R_);
    int ret = 0;
    if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
    if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
    return ret;
  }
  
  void Debug(int now_, int L_, int R_) {
    if (L_ == R_) {
      printf("%d ", sum1[now_]);
      return ;
    }
    Pushdown(now_, L_, R_);
    Debug(ls, L_, mid);
    Debug(rs, mid + 1, R_);
  }
  #undef ls
  #undef rs
  #undef mid
}
bool Check(int lim_) {
  Seg :: Build(1, 1, n, lim_);
  for (int i = 1; i <= m; ++ i) {
    int l = op[i].l, r = op[i].r;
    if (op[i].op) {
      int sum1 = Seg :: Query(1, 1, n, l, r);
      Seg :: Modify(1, 1, n, l, l + sum1 - 1, 1);
      Seg :: Modify(1, 1, n, l + sum1, r, 0);
    } else {
      int sum0 = r - l + 1 - Seg :: Query(1, 1, n, l, r);
      Seg :: Modify(1, 1, n, l, l + sum0 - 1, 0);
      Seg :: Modify(1, 1, n, l + sum0, r, 1);
    }
  }
  return Seg :: Query(1, 1, n, q, q);
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i <= m; ++ i) {
    op[i] = (Operation) {read(), read(), read()};
  }
  q = read();
  for (int l = 1, r = n; l <= r; ) {
    int mid = ((l + r) >> 1);
    if (Check(mid)) { //判断 p 位置是否 >= mid
      ans = mid;
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  printf("%d\n", ans);
  return 0;
}
posted @ 2020-10-01 15:32  Luckyblock  阅读(178)  评论(0编辑  收藏  举报