戳这里,离线做法
给定一个长度为
N
N
N的数组,对
M
M
M个区间
l
∼
r
l \sim r
l∼r 进行排序
求最后
p
p
p 位置的值
排序分(正序和倒序两种)
分析
上面给的链接有离线的做法,算是一个比较套路的做法,用线段树维护
01
01
01序列
可以在
l
o
g
n
logn
logn 的时间内实现排序,所以通过二分最终答案可以得到值
但是这题强制要求在线呢?
这样的话,我们需要使用数据结构维护这些数的状态
如何维护信息
我们思考这样一个过程:
初始我们有
N
N
N个区间,都是有序的(每个数自成一个区间)
一旦将区间
l
∼
r
l \sim r
l∼r正序排序
那么从
l
l
l,
l
+
1
,
.
.
.
,
r
−
1
,
r
l+1,...,r-1,r
l+1,...,r−1,r每个区间都可能不是之前的数了
既然我们看这些长度为
1
1
1的区间暂时得不到结果,我们就将这些区间合并起来看,假如我们能够在很快的时间内将这些数据合并起来,我们知道了区间长度为
r
−
l
+
1
r-l+1
r−l+1,又知道了这些数据合并后的信息,我们岂不是就知道了任意位置
x
x
x 的值,比如
l
l
l 位置就是合并后的最小值,
r
r
r 位置就是合并后的最大值,中间的某个位置就是区间的第
K
K
K大
所以我们要维护的信息就是一颗权值线段树!
由于线段树能够合并!我们的信息也满足短时间(
l
o
g
n
logn
logn)处理完成了
维护的过程
我们继续看,举个确切的例子
给了个数组(这里默认顺序从小到大排序)
5 3 8 2 7 6 1 4
初始区间情况为,每个数自成一个集合
[5] [3] [8] [2] [7] [6] [1] [4]
对区间 [ 2 , 6 ] [2, 6] [2,6] 排序,权值线段树合并后为(如果不会合并的可以看link)
[5] [2, 3, 6, 7, 8] [1] [4]
如果再对区间 [ 7 , 8 ] [7, 8] [7,8] 排序,合并后为
[5] [2, 3, 6, 7, 8] [1, 4]
到这一步仍然没啥问题,很好的解决了排序的问题
细心的你一定发现了,我们上面排序的区间都没有交集,所以很自然的合并了
但是如果我们要对区间
[
2
,
4
]
[2, 4]
[2,4] 进行排序呢?
此时发现,区间
[
2
,
4
]
[2, 4]
[2,4]已经不是由单个小区间组成的了,我们不能直接像上面一样合并区间内的所有小区间
但是我们的思路就是合并啊,这样才能快速得到某个位置的值,我们怎么创造条件让它合并呢?
既然我们可以合并,那么我们是不是也能分裂?
现在考虑分裂!将我们想要的区间从原区间剥离!
我们想要
[
2
,
4
]
[2, 4]
[2,4] 区间,该区间在现在的区间
[
2
,
6
]
[2, 6]
[2,6]中,我们要从
[
2
,
6
]
[2,6]
[2,6]区间中中拿出前三个数(区间
[
2
,
4
]
[2,4]
[2,4]长度为
3
3
3)
在权值线段树中,前三个数就是左子树中的三个数
所以我们构建一颗新的树,来存剥离的信息,假设存在了新的树
p
p
p上,原树存在了下标为
5
5
5 的根所在的树上(
[
2
,
6
]
[2,6]
[2,6]分裂后区间为
[
2
,
4
]
和
[
5
,
6
]
[2,4]和[5,6]
[2,4]和[5,6]),这里可以容易观察到,我们的信息可以存在区间左端点为下标的根所在树上,所以
[
2
,
4
]
[2,4]
[2,4]信息存在下标为
2
2
2的位置,这里只是细节,我们继续讨论过程
分裂后,我们得到了两个小区间,我们想要的区间也是由若干个一整个的区间构成,此时就能执行合并了(这里想要的区间是
[
2
,
4
]
[2,4]
[2,4],分裂后想要的区间刚好由一个
[
2
,
4
]
[2,4]
[2,4]一整个区间组成)
最后合并的结果为
[5] [2, 3, 6] [7, 8] [1, 4]
假如我们要对区间
[
3
,
7
]
[3, 7]
[3,7] 排序呢
我们按照上面的思路,发现想要的区间
[
3
,
7
]
[3,7]
[3,7]由
- 区间 [ 2 , 4 ] [2, 4] [2,4]的一部分
- 区间 [ 5 , 6 ] [5,6] [5,6]的一整个
- 区间 [ 7 , 8 ] [7,8] [7,8]的一部分组成
我们目的是要让排序区间,由若干个一整个的区间构成
对于区间
[
2
,
4
]
[2,4]
[2,4]的一部分,我们根据上面的操作,分裂成
[
2
,
2
]
[2,2]
[2,2] 和
[
3
,
4
]
[3,4]
[3,4]
对于一整个区间
[
5
,
6
]
[5,6]
[5,6]不分裂
对于区间
[
7
,
8
]
[7,8]
[7,8]的那一部分,同样分裂成
[
7
,
7
]
[7,7]
[7,7] 和
[
8
,
8
]
[8,8]
[8,8]
最终我们得到的排序区间
[
3
,
7
]
[3,7]
[3,7]由
[
3
,
4
]
[3,4]
[3,4],
[
5
,
6
]
[5,6]
[5,6],
[
7
,
7
]
[7,7]
[7,7]组成,都是一整个的
所以我们执行合并,完成了排序操作,结果为
[5] [2] [1, 3, 6, 7, 8] [4]
对于倒序排序,影响的只会在分裂的时候,取前 K K K个还是取后 K K K个的抉择
区间是怎么组成的?
届时排序问题已经解决完了,还剩下如何判断我想要的区间是否要分裂?
我们将某个区间合并后,这些原来的某些点的信息都集合在了某棵树上,这棵树上面说了,存在新区间(排序区间)的左端点为根的树上
所以我们要维护,这些点的信息在哪棵树上,方便判断是否需要分裂
这里介绍一个小清新数据结构,珂朵莉树
在数据随机的情况下,支持区间
s
e
t
set
set的操作可以用珂朵莉树在极小常数下完成
这里贴一个珂朵莉树的介绍 这里是链接,现在没有将来可能会有
因为珂朵莉树取一段区间的时候,也需要分裂,所以刚好在珂朵莉树分裂的时候
将维护信息的权值线段树分裂完,合并珂朵莉树的时候,合并权值线段树
珂朵莉树额外记录一个信息,表示正序还是倒序,也就是用于权值线段树分裂的时候取前
K
K
K个还是后
K
K
K个,此时问题解决完毕
我们要找到第
p
o
s
pos
pos个位置是什么树,参考上面的分裂操作
我们将区间按
[
1
,
p
o
s
−
1
]
[
p
o
s
,
N
]
[1,pos-1][pos,N]
[1,pos−1][pos,N]先分裂,之后将区间
[
p
o
s
,
N
]
[pos,N]
[pos,N]分裂为
[
p
o
s
,
p
o
s
]
[pos,pos]
[pos,pos]和
[
p
o
s
+
1
,
N
]
[pos+1,N]
[pos+1,N]
这样我们直接查管理存储
p
o
s
pos
pos位置信息的权值线段树就能得到这个答案了
代码
//P2824
/*
@Author: YooQ
*/
#include <bits/stdc++.h>
using namespace std;
#define sc scanf
#define pr printf
#define ll long long
#define FILE_OUT freopen("out", "w", stdout);
#define FILE_IN freopen("in", "r", stdin);
#define debug(x) cout << #x << ": " << x << "\n";
#define AC 0
#define WA 1
#define INF 0x3f3f3f3f
const ll MAX_N = 5e6+5;
const ll MOD = 1e9+7;
int N, M, K;
struct Seg {
struct Tr {
int k, l, r;
}tr[MAX_N];
int root[MAX_N];
int rcnt = 0;
int indx = 0;
int rabi[MAX_N+10];
int tt = 0;
int mk() {
if (tt) {
return rabi[tt--];
}
return ++indx;
}
void del(int rt) {
if (tt >= MAX_N) return;
tr[rt].k = tr[rt].l = tr[rt].r = 0;
rabi[++tt] = rt;
}
void push_up(int rt) {
tr[rt].k = tr[tr[rt].l].k + tr[tr[rt].r].k;
}
void update(int& rt, int l, int r, int x, int k) {
if (!rt) rt = mk();
if (l == r) {
tr[rt].k += k;
return;
}
int mid = l + ((r-l)>>1);
if (x <= mid) update(tr[rt].l, l, mid, x, k);
if (x > mid) update(tr[rt].r, mid+1, r, x, k);
push_up(rt);
}
int merge(int x, int y, int l, int r) {
if (!x || !y) return x | y;
if (l == r) {
tr[x].k += tr[y].k;
return x;
}
int mid = l + ((r-l)>>1);
tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
tr[x].r = merge(tr[x].r, tr[y].r, mid+1, r);
del(y);
push_up(x);
return x;
}
void splitPre(int x, int& y, int l, int r, int k) {
if (!x || k == tr[x].k) return;
if (!y) y = mk();
if (l == r) {
tr[y].k = tr[x].k - k;
tr[x].k = k;
return;
}
int mid = l + ((r-l)>>1);
int lsum = tr[tr[x].l].k;
if (lsum >= k) swap(tr[x].r, tr[y].r), splitPre(tr[x].l, tr[y].l, l, mid, k);
else splitPre(tr[x].r, tr[y].r, mid+1, r, k - lsum);
push_up(x);
push_up(y);
}
void splitBack(int x, int& y, int l, int r, int k) {
if (!x || k == tr[x].k) return;
if (!y) y = mk();
if (l == r) {
tr[y].k = tr[x].k - k;
tr[x].k = k;
return;
}
int mid = l + ((r-l)>>1);
int rsum = tr[tr[x].r].k;
if (rsum >= k) swap(tr[x].l, tr[y].l), splitBack(tr[x].r, tr[y].r, mid+1, r, k);
else splitBack(tr[x].l, tr[y].l, l, mid, k - rsum);
push_up(x);
push_up(y);
}
int query(int rt, int l, int r) {
if (l == r) return l;
int mid = l + ((r-l)>>1);
if (tr[tr[rt].l].k) return query(tr[rt].l, l, mid);
else return query(tr[rt].r, mid+1, r);
}
}seg;
struct Node{
int l, r;
mutable int k;
Node(int a = -1, int b = -1, int c = -1) {
l = a, r = b, k = c;
}
bool operator < (const Node& B) const {
return l < B.l;
}
};
typedef set<Node>::iterator sit;
struct ODT {
set<Node> st;
sit split(int pos) {
sit it = st.lower_bound(Node(pos));
if (it != st.end() && it->l == pos) return it;
--it;
Node tmp = *it;
if (tmp.k==0) {
seg.splitPre(seg.root[tmp.l], seg.root[pos], 1, N, pos-tmp.l);
} else {
seg.splitBack(seg.root[tmp.l], seg.root[pos], 1, N, pos-tmp.l);
}
st.erase(it);
st.insert(Node(tmp.l, pos-1, tmp.k));
return st.insert(Node(pos, tmp.r, tmp.k)).first;
}
void insert(Node t) {
st.insert(t);
}
}odt;
int arr[MAX_N];
void solve(){
sc("%d%d", &N, &M);
odt.insert({N+1, N+1, 0});
for (int i = 1; i <= N; ++i) {
sc("%d", &arr[i]);
seg.update(seg.root[i], 1, N, arr[i], 1);
odt.insert({i, i, 0});
}
int opt, l, r;
for (int i = 1; i <= M; ++i) {
sc("%d%d%d", &opt, &l, &r);
sit rx = odt.split(r+1);
sit lx = odt.split(l);
lx->k = opt;
for (sit i = ++lx;i!=rx;++i) {
seg.root[l] = seg.merge(seg.root[l], seg.root[i->l], 1, N);
seg.root[i->l] = 0;
}
odt.st.erase(lx, rx);
}
int x;
sc("%d", &x);
odt.split(x+1);
odt.split(x);
pr("%d\n", seg.query(seg.root[x], 1, N));
}
signed main()
{
#ifndef ONLINE_JUDGE
//FILE_IN
FILE_OUT
#endif
int T = 1;//cin >> T;
while (T--) solve();
return AC;
}