「TJOI / HEOI2016」排序
知识点:线段树,二分答案,01 条件转化
学线段树合并屯的题= =
用奇怪的方法过去了。
简述
给定一 \(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;
}
作者@Luckyblock,转载请声明出处。