题目描述 Description
给出N个数,要求做M次区间翻转(如1 2 3 4变成4 3 2 1),求出最后的序列
输入描述 Input Description
第一行一个数N,下一行N个数表示原始序列,在下一行一个数M表示M次翻转,之后的M行每行两个数L,R表示将区间[L,R]翻转。
输出描述 Output Description
一行N个数 , 表示最终序列。
样例输入 Sample Input
4
1 2 3 4
2
1 2
3 4
样例输出 Sample Output
2 1 4 3
数据范围及提示 Data Size & Hint
对于30%的数据满足n<=100 , m <= 10000
对于100%的数据满足n <= 150000 , m <= 150000
对于100%的数据满足n为2的幂,且L = i * 2^j + 1 , R = (i + 1) * 2^j
分类标签 Tags 点此展开
题解:
splay~~~
因为题目中L = i * 2^j + 1 , R = (i + 1) * 2^j的限制,所以给出的反转区间必然是线段树中一个整块,那么我们可以利用这个性质,每次对于需要反转的区间打反转标记,对于线段树中的每一个节点记录他的左右儿子,标记下放的时候,就交换左右儿子,查找的时候<=mid就跳转到左儿子,否则跳转到右儿子。
这样的话就可以直接用线段树来搞,区间打标记然后交换左右儿子就可以了。
AC代码:
跑过全网
#include<cstdio> #include<algorithm> #define lc k<<1 #define rc k<<1|1 using namespace std; inline const int read(){ register int x=0,f=1; register char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int M=2e5+10; const int N=M<<2; int ls[N],rs[N],rev[N],pos[N],a[M]; inline void build(int k,int l,int r){ if(l==r){ pos[k]=l;return ;//只记录下标,自行脑补 } int mid=l+r>>1; ls[k]=lc;rs[k]=rc; build(ls[k],l,mid); build(rs[k],mid+1,r); } inline void pushdown(int k){ if(!rev[k]) return ; rev[k]=0; rev[ls[k]]^=1; rev[rs[k]]^=1; swap(ls[k],rs[k]); } inline void change(int k,int l,int r,int x,int y){ if(l==x&&r==y){ rev[k]^=1;return ; } pushdown(k); int mid=l+r>>1; if(y<=mid) change(ls[k],l,mid,x,y); else if(x>mid) change(rs[k],mid+1,r,x,y); else change(ls[k],l,mid,x,mid),change(rs[k],mid+1,r,mid+1,y); } inline int query(int k,int l,int r,int p){ if(l==r) return pos[k]; pushdown(k); int mid=l+r>>1; if(p<=mid) query(ls[k],l,mid,p); else query(rs[k],mid+1,r,p); } int main(){ int n=read(); for(int i=1;i<=n;i++) a[i]=read(); build(1,1,n); int m=read(); for(int i=1,x,y;i<=m;i++) x=read(),y=read(),change(1,1,n,x,y);//使得根结点右孩子为左子树 for(int i=1,t;i<=n;i++) t=query(1,1,n,i),printf("%d ",a[t]); return 0; }