题意:
给定n和m,表示有一个2n的序列a,接下来进行m次操作,
操作有4种:
(1,x,y):令a(x)=y
(2,k):将所有[(i−1)⋅2k+1,i⋅2k]翻转
(3,k):将所有 [(2i−2)⋅2k+1,(2i−1)⋅2k] 和[(2i−1)⋅2k+1,2i⋅2k]交换
(4,l,r):计算[l,r]的区间和
数据范围:n<=18,m<=1e5,a(i)<=1e9
解法:
问题主要在于如何处理操作2和操作3。
因为给定的序列长度为2n,用线段树做的话就是一棵满二叉树,
令叶子是第0层(因为是20),向上逐层增加。
对于操作2的翻转,其实就是将[0,k]层的每个点的左右子节点交换
对于操作3的交换,其实就是将k+1层的每个点的左右子节点交换
对每层开一个标记,打标记就行了
然后问题变为如何判断正确的操作区间,
例如[1,2]和[3,4]被交换过,我们现在要查询[2,4],实际上是查询[4,4]和[1,2]([2,2]->[4,4],[3,4]->[1,2])
对于操作2和操作3的k,不同子树之间是不会相互影响的,例如:
不管怎么样,x都是不会和y交换的,所以操作2,3交换的两个节点一定在同一个父亲节点下。
下面考虑[l,mid]和[mid+1,r]交换之后,查询的区间[st,ed]会如何变化:
1.如果我们要查询的[st,ed]两端点都在[l,mid]内:
那么实际上是查询:
只是平移了一下而已。两端点都右边的情况同理。
2.如果一个端点在左边一个端点在右边:
拆分成两个询问[st,mid]和[mid+1,ed],这两个询问的两端点都在同一边,那么做法和上面的一样了。
注意点:
应该也看得出来,st和ed一定要在[l,r]范围内,因此线段树函数的要用拆询问的全包含写法。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=3e5+5;
int a[maxm<<2];
int c[maxm<<2];
int rev[20];
int n,m;
int cal(int x,int l,int r){
int mid=(l+r)/2;
if(x<=mid){//左边
return mid+1+(x-l);
}else{//右边
return l+x-(mid+1);
}
}
void pushup(int node){
a[node]=a[node*2]+a[node*2+1];
}
void build(int l,int r,int node){
if(l==r){
cin>>a[node];
c[node]=0;
return ;
}
int mid=(l+r)/2;
build(l,mid,node*2);
build(mid+1,r,node*2+1);
pushup(node);
c[node]=c[node*2]+1;
}
void update(int x,int val,int l,int r,int node){
if(l==r){
a[node]=val;
return ;
}
int mid=(l+r)/2;
if(rev[c[node]]){
x=cal(x,l,r);
}
if(x<=mid)update(x,val,l,mid,node*2);
else update(x,val,mid+1,r,node*2+1);
pushup(node);
}
int ask(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r){
return a[node];
}
int mid=(l+r)/2;
int ans=0;
if(rev[c[node]]){
if(ed<=mid){
ans+=ask(cal(st,l,r),cal(ed,l,r),mid+1,r,node*2+1);
}else if(st>mid){
ans+=ask(cal(st,l,r),cal(ed,l,r),l,mid,node*2);
}else{
ans+=ask(cal(st,l,r),cal(mid,l,r),mid+1,r,node*2+1);
ans+=ask(cal(mid+1,l,r),cal(ed,l,r),l,mid,node*2);
}
}else{
if(ed<=mid){
ans+=ask(st,ed,l,mid,node*2);
}else if(st>mid){
ans+=ask(st,ed,mid+1,r,node*2+1);
}else{
ans+=ask(st,mid,l,mid,node*2)+ask(mid+1,ed,mid+1,r,node*2+1);
}
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m;
n=(1<<n);
build(1,n,1);
while(m--){
int op;cin>>op;
if(op==1){
int x,val;cin>>x>>val;
update(x,val,1,n,1);
}else if(op==2){
int x;cin>>x;
for(int i=0;i<=x;i++){
rev[i]^=1;
}
}else if(op==3){
int x;cin>>x;
rev[x+1]^=1;
}else if(op==4){
int l,r;cin>>l>>r;
int ans=ask(l,r,1,n,1);
cout<<ans<<endl;
}
}
return 0;
}