P2824 【[HEOI2016/TJOI2016]排序】【线段树分裂】+【珂朵莉树】详解

354 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

戳这里,离线做法 给定一个长度为NN的数组,对MM个区间 lrl \sim r 进行排序 求最后 pp 位置的值 排序分(正序和倒序两种)

分析

上面给的链接有离线的做法,算是一个比较套路的做法,用线段树维护0101序列 可以在 lognlogn 的时间内实现排序,所以通过二分最终答案可以得到值

但是这题强制要求在线呢? 这样的话,我们需要使用数据结构维护这些数的状态

如何维护信息

我们思考这样一个过程: 初始我们有NN个区间,都是有序的(每个数自成一个区间) 一旦将区间lrl \sim r正序排序 那么从 ll,l+1,...,r1,rl+1,...,r-1,r每个区间都可能不是之前的数了 既然我们看这些长度为11的区间暂时得不到结果,我们就将这些区间合并起来看,假如我们能够在很快的时间内将这些数据合并起来,我们知道了区间长度rl+1r-l+1,又知道了这些数据合并后的信息,我们岂不是就知道了任意位置 xx 的值,比如 ll 位置就是合并后的最小值rr 位置就是合并后的最大值,中间的某个位置就是区间的第KK

所以我们要维护的信息就是一颗权值线段树! 由于线段树能够合并!我们的信息也满足短时间(lognlogn)处理完成了


维护的过程

我们继续看,举个确切的例子 给了个数组(这里默认顺序从小到大排序)

5 3 8 2 7 6 1 4 初始区间情况为,每个数自成一个集合

[5] [3] [8] [2] [7] [6] [1] [4]

对区间 [2,6][2, 6] 排序,权值线段树合并后为(如果不会合并的可以看link

[5] [2, 3, 6, 7, 8] [1] [4]

如果再对区间 [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,6][2, 6]中,我们要从[2,6][2,6]区间中中拿出前三个数(区间[2,4][2,4]长度为33) 在权值线段树中,前三个数就是左子树中的三个数 所以我们构建一颗新的树,来存剥离的信息,假设存在了新的树 pp上,原树存在了下标为 55 的根所在的树上([2,6][2,6]分裂后区间为[2,4][5,6][2,4]和[5,6]),这里可以容易观察到,我们的信息可以存在区间左端点为下标的根所在树上,所以[2,4][2,4]信息存在下标为22的位置,这里只是细节,我们继续讨论过程

分裂后,我们得到了两个小区间,我们想要的区间也是由若干个一整个的区间构成,此时就能执行合并了(这里想要的区间是[2,4][2,4],分裂后想要的区间刚好由一个[2,4][2,4]一整个区间组成) 最后合并的结果为

[5] [2, 3, 6] [7, 8] [1, 4]

假如我们要对区间 [3,7][3, 7] 排序呢 我们按照上面的思路,发现想要的区间 [3,7][3,7]

  • 区间[2,4][2, 4]的一部分
  • 区间[5,6][5,6]的一整个
  • 区间[7,8][7,8]的一部分组成

我们目的是要让排序区间,由若干个一整个的区间构成 对于区间[2,4][2,4]的一部分,我们根据上面的操作,分裂成[2,2][2,2][3,4][3,4] 对于一整个区间[5,6][5,6]不分裂 对于区间[7,8][7,8]的那一部分,同样分裂成[7,7][7,7][8,8][8,8] 最终我们得到的排序区间[3,7][3,7][3,4][3,4],[5,6][5,6],[7,7][7,7]组成,都是一整个的 所以我们执行合并,完成了排序操作,结果为

[5] [2] [1, 3, 6, 7, 8] [4]

对于倒序排序,影响的只会在分裂的时候,取前KK个还是取后KK个的抉择


区间是怎么组成的?

届时排序问题已经解决完了,还剩下如何判断我想要的区间是否要分裂? 我们将某个区间合并后,这些原来的某些点的信息都集合在了某棵树上,这棵树上面说了,存在新区间(排序区间)的左端点为根的树上 所以我们要维护,这些点的信息在哪棵树上,方便判断是否需要分裂

这里介绍一个小清新数据结构,珂朵莉树 在数据随机的情况下,支持区间setset的操作可以用珂朵莉树在极小常数下完成 这里贴一个珂朵莉树的介绍 这里是链接,现在没有将来可能会有

因为珂朵莉树取一段区间的时候,也需要分裂,所以刚好在珂朵莉树分裂的时候 将维护信息的权值线段树分裂完,合并珂朵莉树的时候,合并权值线段树 珂朵莉树额外记录一个信息,表示正序还是倒序,也就是用于权值线段树分裂的时候取前KK个还是后KK个,此时问题解决完毕

我们要找到第pospos个位置是什么树,参考上面的分裂操作 我们将区间按[1,pos1][pos,N][1,pos-1][pos,N]先分裂,之后将区间[pos,N][pos,N]分裂为[pos,pos][pos,pos][pos+1,N][pos+1,N] 这样我们直接查管理存储pospos位置信息的权值线段树就能得到这个答案了

代码

//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;
}