Tyvj 1729 文艺平衡树
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1
第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n) m表示翻转操作次数
接下来m行每行两个数[l,r] 数据保证 1<=l<=r<=n
输出一行n个数字,表示原始序列经过m次变换后的结果
Sample Input5 3
1 3
1 3
1 4
Sample Output
4 3 2 1 5
Hint
N,M<=100000
sol:splay区间翻转的板子题,似乎搞了很久qaq
要支持在平衡树上打标记,下传的时候就是询问的时候下传,比方说查询第K个的数的位置
翻转的时候打标记就可以了,如果有标记下传的时候交换左右儿子就相当于翻转了(因为是中序遍历)
Q:交换左右儿子以后不满足左边全部比中间小,右边全部大怎么办?
A:不用管,因为没有插入操作了,查找第K个只要管好Size就可以了(确切的说不是第K大的数,只是中序遍历第K个)
Q:怎么找到[l,r]这段区间?
A:找到 l-1 和 r+1 的位置 ll , rr,把 ll 串到根上,这时所有比在 l-1 之后的都在 ll 的右子树里,再把 rr 串到 ll 上所有比l-1后及比rr+1前的就到rr的左子树里了
完结撒花!!!
代码非常丑丑丑丑丑丑丑丑丑丑丑丑丑丑丑
#include <bits/stdc++.h> using namespace std; typedef int ll; inline ll read() { ll s=0; bool f=0; char ch=' '; while(!isdigit(ch)) { f|=(ch=='-'); ch=getchar(); } while(isdigit(ch)) { s=(s<<3)+(s<<1)+(ch^48); ch=getchar(); } return (f)?(-s):(s); } #define R(x) x=read() inline void write(ll x) { if(x<0) { putchar('-'); x=-x; } if(x<10) { putchar(x+'0'); return; } write(x/10); putchar((x%10)+'0'); return; } #define W(x) write(x),putchar(' ') #define Wl(x) write(x),putchar(' ') const int N=200005,inf=0x3f3f3f3f; int n,m; namespace Pht { int Points=0,Root=0; int Child[N][2]; int Parent[N]; int Quanzhi[N]; int Size[N]; bool Rev[N]; inline void Init(); inline int Check(int x); inline void PushUp(int x); inline void PushDown(int x); inline void Rotate(int x); inline void Splay(int At,int To); inline void Insert(int Val); inline int Kth(int Id); inline void OutPut(int Now); inline void Solve(); inline void Init() { Points=Root=0; Insert(-inf); Insert(inf); } inline int Check(int x) { return (Child[Parent[x]][0]==x)?0:1; } inline void PushUp(int x) { Size[x]=Size[Child[x][0]]+Size[Child[x][1]]+1; } inline void PushDown(int x) { if((!x)||(!Rev[x])) return; swap(Child[x][0],Child[x][1]); Rev[x]=0; Rev[Child[x][0]]^=1; Rev[Child[x][1]]^=1; } inline void Rotate(int x) { int y,z,oo; y=Parent[x]; z=Parent[y]; oo=Check(x); Child[y][oo]=Child[x][oo^1]; Parent[Child[x][oo^1]]=y; Child[z][Check(y)]=x; Parent[x]=z; Child[x][oo^1]=y; Parent[y]=x; PushUp(x); PushUp(y); } inline void Splay(int At,int To) { while(Parent[At]!=To) { int Father=Parent[At]; if(Parent[Father]==To) { Rotate(At); } else if(Check(At)==Check(Father)) { Rotate(Father); Rotate(At); } else { Rotate(At); Rotate(At); } } if(To==0) Root=At; } inline void Insert(int Val) { int Now=Root,Par=0; while(Now) { Par=Now; Now=Child[Now][(Val>Quanzhi[Now])?1:0]; } Now=++Points; if(Par) Child[Par][(Val>Quanzhi[Par])?1:0]=Now; Child[Now][0]=Child[Now][1]=0; Parent[Now]=Par; Size[Now]=1; Quanzhi[Now]=Val; Rev[Now]=0; Splay(Now,0); } inline int Kth(int Id) { int Now=Root; for(;;) { PushDown(Now); if(Size[Child[Now][0]]>=Id) { Now=Child[Now][0]; } else if(Size[Child[Now][0]]+1==Id) { return Now; } else if(Size[Child[Now][0]]+1<Id) { Id-=(Size[Child[Now][0]]+1); Now=Child[Now][1]; } } } inline void Reverse(int l,int r) { int ll=Kth(l),rr=Kth(r+2); //找到l-1和r+1的位置 Splay(ll,0); //所有在l-1之后的都到ll的右子树 Splay(rr,ll); //在ll的基础上所有比r+1前的都到rr的左子树,所以[l,r]这段区间就是rr的左子树 Rev[Child[rr][0]]^=1; } inline void OutPut(int Now) { PushDown(Now); if(Child[Now][0]) OutPut(Child[Now][0]); if(Quanzhi[Now]>=1&&Quanzhi[Now]<=n) W(Quanzhi[Now]); if(Child[Now][1]) OutPut(Child[Now][1]); } inline void Solve() { int i; Init(); for(i=1;i<=n;i++) Insert(i); while(m--) { int l=read(),r=read(); Reverse(l,r); } OutPut(Root); } } int main() { R(n); R(m); Pht::Solve(); return 0; } /* input 5 3 1 3 1 3 1 4 output 4 3 2 1 5 */
Ps:我写的似乎是不重的区间翻转,重复的应该也差不多,毕竟只有Insert要用到,大不了一个个插(全是口胡)