Codeforces 1401F 线段树与区间反转

给定一个长2^{n}的数组a,有以下四种操作:

1 x k 把a[x]修改成k

2 k 将数组顺序分为若干个长2^{k}的段,反转每一段的元素

3 k 将数组顺序分为若干个长2^{k}的段,交换第1与第2段,交换第3与第四段.........

4 l r 输出区间[l,r]的元素和

操作1,4是相对容易的,仅需要一些数据结构维护,线段树,树状数组等。

注意到数组长2^{n},意味着每次二分的区间长度都一样,也就是在对应的线段树是一颗满二叉树,然后有注意到,操作2,3修改的区间恰恰在线段树上与某个结点对应(对应的意思是这个区间刚好和某个节点的管理区间相等)。

然后看了答案,反转某一区间,就是把这个区间对应的子树反转(交换这个子树的所有左右儿子),交换相邻区间,就是把管理这两个区间的区间的左右儿子交换,叶子层管理的元素个数是1,每往上一层这个数\times 2,假设这个线段树有一个从叶子往根的深度(叶子节点深度是0),那么这个节点管理的元素个数就是1<<depth,所以:

操作2就是把所有以深度为k的结点为根的子树的所有左右儿子反转

操作3就是把所有深度为k+1的结点的左右儿子反转

所以这棵线段树的左右儿子就不一定是x<<1x<<1|1,我们需要用数组记录左右儿子,一开始是x<<1x<<1|1,交换x结点的左右儿子只需要swap(ls[x],rs[x])

其次,对于线段树的懒标记,保存的地方应该是最不用下传的位置,那就是一起保存在根节点,然后查询的时候下传。对于基础线段树,加法标记还需要有的一个信息是“这个标记该到哪里去?”,这是不容易维护的,所以我们加减标记需要下传到管理这个区间的子区间的结点。因此,当维护的东西可以保存位置信息时,是可以保存在根节点,查询再下传的,对于这题,位置信息很明确,并且维护的是需要交换儿子和不需要交换儿子,那么我们可以状态压缩每个标记,记录两个标记,tag_rev和tag_swap,1左移i位的位置是一代表深度i的结点需要交换儿子(这里是保存在i+1位,也可以保存在i位),同时注意到交换再交换等于不交换,所以更新标记的时候应该用异或操作。

push_down:

tag_rev:

如果现在结点需要交换左右儿子:tag\_rev[x]\&(1<<depth[x])时我们需要交换左右儿子,并且这个时反转标记,现在交换完还需要让两个儿子再交换,所以交换完后把自己的tag上的儿子那个深度的再异或一次再下传给儿子,也可以下传后再在儿子那个位置打上标记

tag_swap:

操作同上,只不过这个只需要交换当前层的左右儿子,不需要给儿子多打一次标记

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll read()
{
	ll ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}

int n,m,a[300005],tag_rev[4000005],tag_swap[4000005];
int ls[4000005],rs[40000005],depth[4000005];
ll tree[4000005];

void push_up(int x){tree[x]=tree[ls[x]]+tree[rs[x]];}

void build(int l,int r,int x)
{
	ls[x]=x<<1;rs[x]=x<<1|1;
	if(l==r)
	{
		tree[x]=a[l];
		return;
	}
	int mid=l+r>>1;
	build(l,mid,ls[x]);
	build(mid+1,r,rs[x]);
	push_up(x);depth[x]=depth[ls[x]]+1;
}

void push_down(int x)
{
	if(tag_rev[x])
	{
		if(tag_rev[x]&(1<<depth[x]))
		{
			swap(ls[x],rs[x]);
			tag_rev[x]^=1<<depth[x]-1;
		}
		tag_rev[ls[x]]^=tag_rev[x];
		tag_rev[rs[x]]^=tag_rev[x];
		tag_rev[x]=0;
	}
	if(tag_swap[x])
	{
		if(tag_swap[x]&(1<<depth[x])) swap(ls[x],rs[x]);
		tag_swap[ls[x]]^=tag_swap[x];
		tag_swap[rs[x]]^=tag_swap[x];
		tag_swap[x]=0;
	}
}

void update(int nl,int nr,int l,int r,int x,ll k)
{
	if(nl<=l&&r<=nr)
	{
		tree[x]=k;//单点修改不用乘区间长
		return;
	}
	int mid=l+r>>1;push_down(x);
	if(nl<=mid) update(nl,nr,l,mid,ls[x],k);
	if(nr>mid)  update(nl,nr,mid+1,r,rs[x],k);
	push_up(x);
} 

ll query(int nl,int nr,int l,int r,int x)
{
	if(nl<=l&&r<=nr) return tree[x];
	int mid=l+r>>1;push_down(x);ll ret=0;
	if(nl<=mid) ret+=query(nl,nr,l,mid,ls[x]);
	if(nr>mid)  ret+=query(nl,nr,mid+1,r,rs[x]);
	return ret;
}

int main()
{
	n=1<<read();int m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,n,1);
	while(m--)
	{
		int op=read();
		if(op==1)
		{
			int x=read();
			update(x,x,1,n,1,read());
		}
		else if(op==2) tag_rev[1]^=1<<read();
		else if(op==3) tag_swap[1]^=1<<read()+1;
		else
		{
			int l=read(),r=read();
			printf("%lld\n",query(l,r,1,n,1));
		}
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值