题面链接
※本题思路来自他人启发,并非个人思路还有这是个魔鬼模板题吧!唉,是我太菜了
最开始…
啥都没想到,请教LJZ大神,又听老师讲解了一下,恍然大悟。
正文开始
按题目推荐的做法,就用平衡树。由于本题要求的操作是区间翻转,考虑到普通Treap的旋转令我很是尴尬:
例如序列1 2 3 4 5,区间翻转1~4变为4 3 2 1 5
能做到的大佬请在评论区留言,教教本蒟蒻!!!
区间翻转在树上操作的特性
所以普通Treap想不出,就用不需要旋转的FHQ TreapSplay我还没学会。
由于平衡树满足BST的性质,在这个翻转的序列里,只有位置是不变的,位置上的数是变化的,所以我们要维护位置的有序性,而不是维护数字的有序性。
所以可以想到,大概要实现这样的操作:
序列1 2 3 4 5,1~5翻转得到5 4 3 2 1(红色是位置,蓝色是数值)
仔细观察,可以发现,翻转的本质应是交换左右儿子,正如这一个全序列翻转,只要将树中所有根结点的左右儿子交换,新树的中序遍历就是答案。再多画几个图,我们可以发现,如果有一棵BST按上面的规定存起翻转的区间,并交换树中每个根结点的左右儿子,这个区间就翻转成功。
初步想法
由上面可以想到一个初步做法:数组装起初始序列,每一次给出一个区间,将这一区间的数字装进一棵平衡树,将树中所有根节点的左右儿子交换,通过中序遍历更新这一区间的序列。
在
[
L
,
R
]
[L,R]
[L,R]的区间中,给平衡树插入
R
−
L
R-L
R−L个数(估算:
Θ
(
log
(
R
−
L
+
1
)
)
\Theta (\log (R-L+1))
Θ(log(R−L+1)),最坏结果
Θ
(
log
n
)
\Theta (\log n)
Θ(logn)),又交换所有根结点的左右儿子(估算:最坏结果
Θ
(
n
2
)
\Theta (\frac{n}{2})
Θ(2n)即
Θ
(
n
)
\Theta (n)
Θ(n)),并中序遍历一次(估算:
O
(
n
)
O(n)
O(n)),更新一次序列(估算:
O
(
n
)
O(n)
O(n)),估算一下,一次翻转大概是
O
(
log
n
+
n
)
O(\log n + n)
O(logn+n)em…大概是这样吧…
m
m
m次操作下来就大概是
O
(
log
n
m
+
n
m
)
O(\log nm + nm)
O(lognm+nm)本蒟蒻不是很会算,如果有错敬请指正
然而:
【数据范围】
对于 100% 的数据, 1 ≤ n , m ≤ 100000 , 1 ≤ l ≤ r ≤ n 1 \le n, m \leq 100000,1 \le l \le r \le n 1≤n,m≤100000,1≤l≤r≤n。
爆了!爆了!
数据结构优化
首先,为了避免每次建树花费的大量时间,我们可以只建一棵树,在每一次操作的时候,将这棵树用FHQ Treap的核心操作Split将原树拆成三棵树:储存着区间
[
1
,
L
−
1
]
[1,L-1]
[1,L−1],
[
L
,
R
]
[L,R]
[L,R],
[
R
−
1
,
n
]
[R-1,n]
[R−1,n],只对
[
L
,
R
]
[L,R]
[L,R]这棵树操作就行了 (当时天真的我认为一个Merge就能打天下,结果碰上Split把我急了半天,最后又被自己打脸,发现Merge真的可以打天下,因为我徒手敲了个基于Merge的Split,哈哈哈) 操作完以后再用FHQ Treap的核心操作Merge把这三棵树再次合并,就得到新序列了。
为了减小我们的操作时间,看这个区间,看这个翻转,是不是有种线段树的感觉?为了节省翻转所需的时间,我们不妨继承线段树的优点:Tag。当时想到这里的我,还是一脸蒙蔽,打标记?真的有用吗?怎么用标记啊?
我们不妨取出翻转区间,给此区间的根结点打上标记,如果再次访问到这一结点就对它的左右儿子进行翻转,并把标记下传给它的儿子。这样我们就不用每次都翻转,在最后输出答案时一次性把剩下该翻转的都反转了,就能大大减小时间复杂度了。
文字表述和图画表述还不是很方便、清晰啊!大家尽力理解一下吧!
更多具体解读详看代码注释!
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<ctime>
using namespace std;
int n,leftn,rightn,m;
struct node{
node *lson,*rson;
int pri;//优先级
int siz;//树的大小
int val;//数值
int mark;//翻转标记
void getsize()//获取树的大小
{
siz = 1;
if(lson) siz += lson->siz;
if(rson) siz += rson->siz;
}
void passmark()//下传标记
{
mark ^= 1;
if(lson) lson->mark ^= 1;
if(rson) rson->mark ^= 1;
}
}*root , mem[23333333], *now = mem;
node *newnode(int num)//新建结点
{
node *tmp = now++;
tmp->lson = tmp->rson = NULL;
tmp->val = num;
tmp->pri = rand();
tmp->siz = 1;
tmp->mark = 0;
return tmp;
}
void Reverse(node *&T)//翻转
{
//等价于swap(T->lson,T->rson);
node *tmp = T->lson;
T->lson = T->rson;
T->rson = tmp;
T->passmark();//记得下传标记
return ;
}
node *Merge(node *L,node *R)//核心操作Merge
{
if(L == NULL) return R;
if(R == NULL) return L;
if(L->pri > R->pri)
{
if(L->mark) Reverse(L);//反转操作
L->rson = Merge(L->rson,R);
L->getsize();
return L;
}
else{
if(R->mark) Reverse(R);//反转操作
R->lson = Merge(L,R->lson);
R->getsize();
return R;
}
}
node *Insertx(node *&p,int num)//基于Merge的Insert操作
{
if(p == NULL)
{
p = newnode(num);
return p;
}
if(num < p->val)
{
node *newson = Insertx(p->lson,num);
p->lson = NULL;
p->getsize();
return Merge(newson,p);
}
if(num > p->val)
{
node *newson = Insertx(p->rson,num);
p->rson = NULL;
p->getsize();
return Merge(p,newson);
}
}
//基于Merge的Split操作
//徒手敲时,为防止出错,详细写了一遍注释
void Split(node *&T,int k,node *&newt/*new tree*/,node *&remt/*remain tree*/)
//把一棵树,拆成由前k个数组成的树和剩下部分
{
int lsize = 0;
if(T->mark) Reverse(T);反转操作(先)
if(T->lson) lsize = T->lson->siz;//获取左子树大小用于计算排名(后)
if(lsize >= k)//左子树大小比k大,第k个数一定在左子树里面
{
if(T->lson) Split(T->lson , k , newt, remt);//判断它有左子树,才在左子树拆树
T->lson = NULL;
remt = Merge(remt,T);//将其本身和右子树并入右树
if(remt) remt->getsize();//记得更新子树大小,小心空指针
return ;
}
if(lsize + 1 < k)//在新树范围内,但还没到最后一个结点
{
if(T->rson) Split(T->rson,k - lsize - 1,newt,remt);//在右子树继续拆树
T->rson = NULL;
//因为已经在右子树拆过树,newt中的所有结点都来自于T右子树,又根据BST性质,所以newt中的所有的值 都比 当前没有右儿子的T中的所有的值要大
newt = Merge(T ,newt );//把其本身左子树全部合并
if(newt) newt->getsize();//记得更新子树大小
return ;
}
if(lsize + 1 == k)//找到最后一个结点拆树到此为止
{
node *tmp = T->rson;
T->rson = NULL;
newt = Merge(T,newt);//左儿子和它本身并入新树
if(newt) newt->getsize(); //记得更新子树大小
remt = Merge(remt,tmp);//右儿子并入旧树
if(remt) remt->getsize();//记得更新子树大小
return ;
}
}
void travel(node *p)//中序遍历
{
if(p->mark) Reverse(p);
if(p->lson) travel(p->lson);
cout<<p->val<<' ';
if(p->rson) travel(p->rson);
}
int main()
{
srand((unsigned)time(NULL));
node *root = NULL ,*root1 = NULL ,*root2 = NULL ,*root3 = NULL ,*root4 = NULL ;
cin >> n >> m;
for(int i=1;i<=n;i++) root = Insertx(root , i);//插入原始序列
for(int i=1;i<=m;i++)
{
root1 = root2 = root3 = root4 = NULL;//初始化
cin >> leftn >> rightn;
if(leftn != 1 && rightn != n)//emm....由于赶时间,直接特判
{
Split(root,leftn-1,root1,root2);//[1,n]分成[1,L-1],[L,n]
Split(root2,rightn-leftn+1,root3,root4);//[L,n]分成[L,R],[R+1,n]
root3->mark = 1;
root = Merge(root1,root3);//合并回去
root = Merge(root,root4);
}
else
{
if(leftn == 1 && rightn == n)
{
root->mark = 1;
Reverse(root);//反转操作
}
else
{
if(leftn == 1)
{
Split(root,rightn,root1,root2);
root1->mark = 1;
root = Merge(root1,root2);
}
else if(rightn == n)
{
Split(root ,leftn-1,root1,root2);
root2->mark = 1 ;
root = Merge(root1,root2);
}
}
}
}
travel(root);
return 0;
}