伸展树。。。
赤果果地抄袭雷哥的代码:http://www.cnblogs.com/jianglangcaijin/archive/2013/01/21/2869148.html
题意:输入n,m。给你n个数,初始排列是1到n,然后有m个操作,每次操作给两个数L,R,把(L,R)区间上的数的顺序颠倒,输出最终的顺序。
伸展树就是二叉查找排序数,一个节点的左子树上的数都比它小,右子树上的数比它大。这道题要在首尾加上哨兵,即有n+2个数,操作(L,R),就是把L-1旋转至根节点,然后把R+1旋转至L-1的右子树,那么R+1的左子树就是区间(L,R),然后把这个区间颠倒。
因为是第一次看伸展树的代码,雷哥定义的数组看了好大一会儿才知道干吗用的。
c[f][0]是节点f的左子树,c[f][1]就是节点f的右子树。p[x]是节点x的父节点,s[x]是以节点x为跟的数的节点数,sign[x]表示节点x是否颠倒,和线段树有点像。
下面就贴代码了,函数的功能也在代码里注释。
#include<cstdio> #include<string> #include<algorithm> using namespace std; const int N=130005; int n,m; int root; int sign[N],c[N][2],s[N],p[N]; void down(int x) //将以节点x为根的树颠倒 { if(!sign[x]) return ; sign[x]=0; swap(c[x][0],c[x][1]); sign[c[x][0]]^=1; sign[c[x][1]]^=1; } void update(int f,int x,int flag)//建立节点x,使它的父节点为f,flag表示它是左子树还是右子树 { p[x]=f; if(!f) return ; c[f][flag]=x; s[f]=1+s[c[f][0]]+s[c[f][1]]; } int build(int L,int R) //建树 { if(L>R) return 0; int m=(L+R)>>1; update(m,build(L,m-1),0); update(m,build(m+1,R),1); return m; } void rotate(int x) //旋转节点x { int P=p[x],G=p[P]; if(c[P][0]==x)//如果x是P的左子树 { update(P,c[x][1],0);//x的右子树转为P的左子树 update(x,P,1);//P成为x的右子树 update(G,x,!(c[G][0]==P)); } else { update(P,c[x][0],1); update(x,P,0); update(G,x,!(c[G][0]==P)); } } void splay(int x,int &goal) { int P=p[x],G=p[P],limit=p[goal]; while(P!=limit) { if(G!=limit && (c[G][0]==P)==(c[P][0]==x)) rotate(P); rotate(x); P=p[x]; G=p[P]; } goal=x; down(goal); } int select(int x) { int t=root; while(1) { down(t); if(s[c[t][0]]+1==x) break; if(s[c[t][0]]+1>x) t=c[t][0]; else x-=s[c[t][0]]+1,t=c[t][1]; } return t; } int main() { scanf("%d%d",&n,&m); root=build(1,n+2); int l,r; while(m--) { scanf("%d%d",&l,&r); int r1=select(l); int r2=select(r+2); splay(r1,root); splay(r2,c[r1][1]); int x=c[root][1]; x=c[x][0]; sign[x]^=1; } for(int i=2;i<=n+1;i++) printf("%d ",select(i)-1); puts(""); return 0; }