【codevs1743】反转卡片
题目描述 Description
【dzy493941464|yywyzdzr原创】
小A将N张卡片整齐地排成一排,其中每张卡片上写了1~N的一个整数,每张卡片上的数各不相同。
比如下图是N=5的一种情况:3 4 2 1 5
接下来你需要按小A的要求反转卡片,使得左数第一张卡片上的数字是1。操作方法:令左数第一张卡片上的数是K,如果K=1则停止操作,否则将左数第1~K张卡片反转。
第一次(K=3)反转后得到:2 4 3 1 5
第二次(K=2)反转后得到:4 2 3 1 5
第三次(K=4)反转后得到:1 3 2 4 5
可见反转3次后,左数第一张卡片上的数变成了1,操作停止。
你的任务是,对于一种排列情况,计算要反转的次数。你可以假设小A不会让你操作超过100000次。
输入描述 Input Description
第1行一个整数N;
第2行N个整数,为1~N的一个全排列。
输出描述 Output Description
仅1行,输出一个整数表示要操作的次数。
如果经过有限次操作仍无法满足要求,输出-1。
样例输入 Sample Input
5
3 4 2 1 5
样例输出 Sample Output
3
数据范围及提示 Data Size & Hint
0<N≤300,000。
题解:两种方法,rope,十分简单的,c++#include<ext/rope>可持久化平衡树,。
splay,区间旋转在其实就想到了splay吧,每次lg,常数大一些。
红色代表虚点,黑色代表实点,先翻转成这个样子,然后对于根的右子树的左子树的根打上旋转标记
即可。
旋转标记
代码注释了不少
rope代码
1 #include<cstring> 2 #include<cmath> 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 #include<ext/rope> 7 #include<ext/hash_map> 8 9 #define N 300007 10 using namespace std; 11 using namespace __gnu_cxx; 12 inline int read() 13 { 14 int x=0,f=1;char ch=getchar(); 15 while(ch<'0'||ch>'9'){if (ch=='-')f=-1;ch=getchar();} 16 while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} 17 return x*f; 18 } 19 20 int n,ans; 21 int a[N]; 22 rope<int>s1,s2,t1,t2; 23 24 void spin(int x) 25 { 26 t1=s1.substr(0,x); 27 t2=s2.substr(n-x,x);//后者是长度 28 s1=t2+s1.substr(x,n-x); 29 s2=s2.substr(0,n-x)+t1; 30 } 31 int main() 32 { 33 n=read(); 34 for (int i=1;i<=n;i++)a[i]=read(); 35 for (int i=1;i<=n;i++)s1.push_back(a[i]); 36 for (int i=1;i<=n;i++)s2.push_back(a[n-i+1]); 37 while(s1[0]!=1) 38 { 39 spin(s1[0]); 40 ans++; 41 if (ans>100000) 42 { 43 printf("-1 "); 44 return 0; 45 } 46 } 47 printf("%d ",ans); 48 }
splay代码
1 #include<cstring> 2 #include<cmath> 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 7 #define N 300007 8 using namespace std; 9 inline int read() 10 { 11 int x=0,f=1;char ch=getchar(); 12 while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();} 13 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} 14 return x*f; 15 } 16 17 int n,ans,rt; 18 int a[N]; 19 int c[N][2],fa[N],siz[N],val[N]; 20 bool rev[N]; 21 22 void update(int k) 23 { 24 int l=c[k][0],r=c[k][1]; 25 siz[k]=siz[l]+siz[r]+1; 26 } 27 void rotate(int x,int &k) 28 { 29 int y=fa[x],z=fa[y],l,r; 30 if (c[y][0]==x)l=0;else l=1;r=l^1; 31 if (y==k) k=x; 32 else 33 { 34 if (c[z][0]==y) c[z][0]=x; 35 else c[z][1]=x; 36 } 37 fa[x]=z,fa[y]=x,fa[c[x][r]]=y; 38 c[y][l]=c[x][r],c[x][r]=y; 39 update(y),update(x); 40 } 41 void splay(int x,int &p) 42 { 43 while(x!=p) 44 { 45 int y=fa[x],z=fa[y]; 46 if (y!=p) 47 { 48 if (c[y][0]==x^c[z][0]==y) rotate(x,p); 49 else rotate(y,p); 50 } 51 rotate(x,p); 52 } 53 } 54 void pushdown(int k) 55 { 56 int l=c[k][0],r=c[k][1]; 57 rev[k]^=1,rev[l]^=1,rev[r]^=1; 58 swap(c[k][0],c[k][1]); 59 } 60 void build(int l,int r,int p) 61 { 62 if (l>r) return; 63 int mid=(l+r)>>1; 64 if (mid<p) c[p][0]=mid; 65 else c[p][1]=mid; 66 siz[mid]=1,val[mid]=a[mid],fa[mid]=p; 67 if (l==r) return; 68 build(l,mid-1,mid),build(mid+1,r,mid); 69 update(mid); 70 } 71 int find(int p,int rk)//寻找第rk的位置。 72 { 73 if (rev[p]) pushdown(p); 74 int l=c[p][0],r=c[p][1]; 75 if (siz[l]+1==rk) return p; 76 else if (siz[l]>=rk) return find(l,rk); 77 else return find(r,rk-siz[l]-1); 78 } 79 void spin(int l,int r) 80 { 81 int x=find(rt,l),y=find(rt,r+2);//因为加了两个虚节点,所以翻转的时候方便,1为虚节点,将2----当前位置+1这一段旋转出来。 82 splay(x,rt),splay(y,c[x][1]);//这样根的右子树的左子树就是所求区间。 83 int z=c[y][0]; 84 rev[z]^=1;//在这个节点上打上标记,可以想一下,现在树是什么样子的。 85 } 86 int main() 87 { 88 n=read(); 89 for (int i=1;i<=n;i++) a[i+1]=read(); 90 build(1,n+2,0),rt=(1+n+2)>>1;//0只是虚的点,没有用的,1和n+2是哨兵,一线段树的形式建立splay先。 91 while(val[find(rt,2)]!=1)//第二个是第一个数 92 { 93 ans++; 94 spin(1,val[find(rt,2)]); 95 if (ans>100000) 96 { 97 printf("-1 "); 98 return 0; 99 } 100 }//操作直到第一个是1。 101 printf("%d ",ans); 102 }
发现手写splay快,常数大但是比c++的大部分的stl还是快的,stl没有开O3,O2
是很慢的,而且可持久化平衡树更强大,就需要更大的代价。