排过序的区间可以视为整体,某些区间的某些部分被重新排序相当于分裂和合并
可以用权值线段树维护排序后的区间内的权值。
与此同时我们需要完成线段树和位置的对应
一开始,为了方便,不妨直接开 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;
}