Codeforces1401 F. Reverse and Swap(线段树)

题意:

给定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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值