【Luogu2824】【HEOI2016/TJOI2016】排序 【二分答案+线段树 / 权值线段树分裂&合并】

[ L i n k ] [\frak{Link}] [Link]


排过序的区间可以视为整体,某些区间的某些部分被重新排序相当于分裂和合并
可以用权值线段树维护排序后的区间内的权值。
与此同时我们需要完成线段树和位置的对应


一开始,为了方便,不妨直接开 n 个线段树分别维护每个位置
当对一段区间排序时,可能区间内会有 n 多个线段树和至多两棵要被打折的线段树
对于那 n 多棵线段树直接合并
剩下要被砍断的就 Find 然后 Split + Merge。

为了 Find 应当维护每棵线段树对应区间的左边界
假如现在排序 [L,R] 那么如果被切的区间升序那么需要 FInd_KthMin,K = (R - LBorder + 1)
降序:Find_KthMax,K = (R - LBorder + 1)
接着 Split + Merge 的工作不妨直接把 LBorder~R 给 Merge 出去

一开始会多出 n 棵线段树
这 n 棵线段树被处理掉需要 O ( n log ⁡ n ) O(n\log n) O(nlogn)
接下来每次排序至多多出两棵新的线段树 O ( m log ⁡ n ) O(m\log n) O(mlogn)

但是怎么找到这些线段树呢?

可以维护一个 Priority_Queue 记录出现过的线段树的左端点

我更建议用 stl 的 set 比较简洁。另外,不妨直接将左端点位置作为线段树的编号。

但是注意 lower_bound 的时候千万不要用 lower_bound(s.begin(), s.end(), elem)
STL set is using non-random-access iterators. std::set::iterator is bidirectional.
正确复杂度的写法应该是 s.lower_bound(elem)

s.erase(iterator_first, iterator_last) 的写法单次复杂度是 O(n) 级别的。
包括 ++iterator 是 O(log n) 级别的……
但是类似上面那样分析得到,总的复杂度并不会上天、

权值线段树在这里充当了维护桶排的数据结构

没写垃圾回收。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iomanip>
#include<set>
using namespace std;

const int MAXN = 1e5 + 10;
震惊!!const int MAXXN = MAXN << 6; 
int n, m, Total = 0;
int LChild[MAXXN], RChild[MAXXN], Size[MAXXN], Root[MAXN];
bool Operated_Type[MAXN];
set<int> SegmentTrees;

void Insert(int &Pos, int L, int R, int Value)
{
	Size[Pos = ++Total] = 1;
	if (L < R)
	{
		int Mid = L + R >> 1;
		if (Value <= Mid) Insert(LChild[Pos], L, Mid, Value);
		else Insert(RChild[Pos], Mid + 1, R, Value);
	}
}

void Split(int PosLeft, int &PosRight, int Kth, bool _Scending)
{
	if (Size[PosLeft] == Kth) return;
	Size[PosRight = ++Total] = Size[PosLeft] - Kth;
	Size[PosLeft] = Kth;
	if (_Scending == 0)
	{
		// Ascending
		if (Kth <= Size[LChild[PosLeft]])
			Split(LChild[PosLeft], LChild[PosRight], Kth, 0), RChild[PosRight] = RChild[PosLeft], RChild[PosLeft] = 0;
		else
			Split(RChild[PosLeft], RChild[PosRight], Kth - Size[LChild[PosLeft]], 0);
	}
	else
	{
		// Descending
		if (Kth <= Size[RChild[PosLeft]])
			Split(RChild[PosLeft], RChild[PosRight], Kth, 1), LChild[PosRight] = LChild[PosLeft], LChild[PosLeft] = 0;
		else
			Split(LChild[PosLeft], LChild[PosRight], Kth - Size[RChild[PosLeft]], 1);
	}
}

void Merge(int &PosSub, int PosAtt)
{
	if (!PosAtt) return;
	if (!PosSub) { PosSub = PosAtt; return;}
	Size[PosSub] += Size[PosAtt];
	Merge(LChild[PosSub], LChild[PosAtt]);
	Merge(RChild[PosSub], RChild[PosAtt]);
}

set<int>::iterator SplitMain(int Pos)
{
	set<int>::iterator i = SegmentTrees.lower_bound(Pos);
	if (*i == Pos) return i;
	--i;
	Split(Root[*i], Root[Pos], Pos - *i, Operated_Type[Pos] = Operated_Type[*i]);
	return SegmentTrees.insert(Pos).first;
	// SegmentTrees.insert(Pos) returns pair<set<int>::iterator, bool>
}

void Sort(int OperationType, int L, int R)
{
	if (L==R) return;
	// 0 Ascending; 1 Descending
	set<int>::iterator iL, iR;
	iL = SplitMain(L);
	iR = SplitMain(R+1);
	for (set<int>::iterator i = ++iL; i != iR; ++i) // Don't use 'i < iR'
	{
//		cout << *i << " " << L << " " << Root[*i] << " " << Root[L] << " " << Size[Root[*i]] << " " << Size[Root[L]] << "FAFA\n";
		Merge(Root[L], Root[*i]);
	}
	SegmentTrees.erase(iL, iR);
	Operated_Type[L] = OperationType;
}

int FindExisting(int Pos, int L, int R)
{
	if (L==R) return L;
	int Mid = L + R >> 1;
	if (Size[LChild[Pos]]) return FindExisting(LChild[Pos], L, Mid);
	return FindExisting(RChild[Pos], Mid + 1, R);
}

int main()
{
	scanf("%d%d", &n, &m);
	SegmentTrees.insert(n+1);
	// if not indexes may be out of bounds
	
	for (int a, i = 1; i <= n; ++i)
	{
		scanf("%d", &a);
		SegmentTrees.insert(i);
		Insert(Root[i], 1, n, a);
	}
	
	for (int op, l, r, i = 1; i <= m; ++i)
	{
		scanf("%d%d%d", &op, &l, &r);
		Sort(op, l, r);
	}
	
	int q;
	scanf("%d", &q);
	
	像平衡树一样地完成最后的查询操作
	SplitMain(q);
	SplitMain(q+1);
	printf("%d", FindExisting(Root[q], 1, n));
	
	return 0;
}

第二种做法:
这道题有一个很奇怪的地方是它只会查询一次
或许可以利用这一次查询,寻找一种离线的做法,简化修改
它会查询的是 位置 q 上的数字
假如知道这个数字的话,只需要根据这个数字值的大小划分,问题会变得相当简单。

可是我们不知道(
如果二分答案呢?
如果二分的 Mid 正确,最后位置 q 上的数字应该 = Mid。
如果二分的 Mid 小了,最后 < ;如果大了最后 >
不妨标记 ≥ Mid 为 1, < Mid 为 0
所以只需要找到第一个恰好位置 p 被标记为 1 的就可以了

二分答案之后问题转化为对 01 序列进行区间排序,就水了
复杂度 O ( m log ⁡ 2 n ) O(m\log^2 n) O(mlog2n)
记得 Lazytag

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iomanip>
#include<set>
using namespace std;
int n, m;
int main()
{
	scanf("%d%d", &n, &m);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值