题目
文艺平衡树
https://www.luogu.org/problemnew/show/P3391
此程序还可以提交 BZOJ-3223
分析
- 第一次写splay,程序里有双旋和单旋两种,不过luogu上时间好像差别不大……(甚至好像双旋还更慢?)
- 本来写旋转的时候没用到 & 这一点,后来看到某篇博客上面,才发现这个技巧,可以有效地减少代码量。
- 弄个 tag 说明此字树需要翻转,下传的时候把其左右儿子交换即可。
- 要是有 dalao 可以帮我修改一下,那也谢谢了!(看了一下其他人的时间,好像比我的快好多……)
程序
#include <cstdio>
#include <algorithm>
using namespace std;
int fa[100005],ls[100005],rs[100005],sz[100005],tag[100005],root,n,m;
void mer(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;}
void bui(int l,int r,int Fa){ //建立一棵相对平衡的二叉树
if (l>r) return;
int mid=l+r>>1;
fa[mid]=Fa;
if (mid>Fa) rs[Fa]=mid;else ls[Fa]=mid;
bui(l,mid-1,mid); bui(mid+1,r,mid);
mer(mid);
}
void node_up(int x){ //在不改变中序的前提下把节点 x 往上弄一层
int Fa=fa[x],L=ls[x],R=rs[x];
if (Fa==ls[fa[Fa]]) ls[fa[Fa]]=x;else rs[fa[Fa]]=x;
if (x==ls[Fa]){
fa[x]=fa[Fa],fa[Fa]=x,fa[R]=Fa;
rs[x]=Fa,ls[Fa]=R;
}else{
fa[x]=fa[Fa],fa[Fa]=x,fa[L]=Fa;
ls[x]=Fa,rs[Fa]=L;
}
mer(x); mer(Fa);
if (fa[x]==0) root=x;
}
void tag_add(int x){tag[x]^=1;}
void tag_down(int x){
if (tag[x]){
swap(ls[x],rs[x]);
tag[ls[x]]^=1;
tag[rs[x]]^=1;
tag[x]=0;
}
}
int find(int k){ //找到当前序列中第 k 个数
//由于多设了一个 0 节点,所以只需 cnt==k (中序遍历中左边有 k 个数)就是实际序列中第 k 个数
for (int x=root,cnt=-1; cnt!=k; ){
tag_down(x);
if (x==ls[fa[x]]) cnt-=sz[rs[x]]+1;
else cnt+=sz[ls[x]]+1;
if (cnt==k) return x;
x=(cnt<k) ? rs[x]:ls[x];
}
}
void To_(int x,int &goal){ //单旋
if (x==goal) return;
while (goal!=x) node_up(x);
}
void To(int x,int &goal){ //双旋
while (goal!=x){
if (fa[x]==goal) {node_up(x); break;}
node_up(fa[x]);
node_up(x);
}
}
void dfs(int x){
if (!x) return;
tag_down(x);
dfs(ls[x]); if (x>1 && x<=n+1) printf("%d ",x-1); dfs(rs[x]);
}
int main(){
//freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
root=n+3>>1; bui(1,n+2,0); //加入节点 0 和 n+1,然后为了代码方便,把每个节点的键值加一
for (int l,r,L,R; m--; ){
scanf("%d%d",&l,&r);
L=find(l-1); To(L,root); //把 (l-1) 弄到根节点上
R=find(r+1); To(R,rs[L]); //把 (r+1) 弄到根节点的右儿子上
tag_add(ls[R]); //打上翻转标记
}
dfs(root); //输出
}