【线段树】洛谷P3870 开关题解

P3870 [TJOI2009] 开关 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意

给定一段区间[l,r],对该区间的灯作相反操作(开的变关,关的变开)。

思路

如果暴力模拟,那肯定会超时,不然题目给个线段树的标签干什么。

而线段树的优点正是可以快速地对一段区间进行同样操作,并且其时间复杂度降到了\Theta ({log_{2}}^{n})

先复习一下线段树吧,虽然我也刚学不久线段树的用处就是对编号连续的一些点进行修改或者统计操作,本质上是维护下标为1,2,..,n的n个按顺序排列的数的信息,即是维护n的个点的信息,至于每个点的数据的含义可以有很多。

把一段区间[l,r]不断分成[l,mid][mid+1,r]两段区间,其中mid=\frac{l+r}{2}(整数除法,而且有点分治那味了),而且对任意的n的分解都是唯一的,我的理解就是改变了到达某一个下标的方式。

在做操作的同时,还引入了一个叫做“懒惰标记”的东西,它起到了延迟操作,防止操作堆积的作用。

简要讲讲就够,讲的也不太好。

因为灯要开关,因此想到物理中的高低电平,可以用10分别表示灯的开和关的状态。

但是考虑到:按照正常人的思维,一般会通过判断来实现10之间的转化,未免太麻烦了。于是可以用一种奇妙的方法:异或(^)

当状态为1时,对它^=1,就有1 xor 1=0

当状态为0时,对它^=1,就有0xor1=1

这样就易如反掌的实现了开关灯的操作(其实1和-1转换应该也可以),而且也方便了更新状态,减少了大幅的码量!

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll z=500005;
ll n,m,mod,a[z],m1[z],m2[z],tree[z],zt,x,y,ans;
//线段树搓板子,不过将+改为了^
void pushdown(ll p,ll l,ll r)//更新,此处为异或1,因此对懒惰节点的更新方式为^=1
{
	if(m2[p]==0)return;
	m2[p*2]^=1,m2[p*2+1]^=1;//lazytag
	ll mid=(l+r)/2;
	tree[p*2]=(mid-l+1)-tree[p*2];
	tree[p*2+1]=(r-mid)-tree[p*2+1];
	m2[p]=0;
}
void addpoint2(ll l,ll r,ll p,ll nowl,ll nowr)//作异或
{
	if(l<=nowl&&r>=nowr)//区间包含
	{
		tree[p]=(nowr-nowl+1)-tree[p];
		m2[p]^=1;
		return;
	}
	pushdown(p,nowl,nowr);//否则更新节点,准备继续分解
	ll mid=(nowl+nowr)/2;
	if(l<=mid)addpoint2(l,r,p*2,nowl,mid);//左子树
	if(r>mid)addpoint2(l,r,p*2+1,mid+1,nowr);//右子树
	tree[p]=tree[p*2]+tree[p*2+1];//父节点收集左右两个子节点的信息
}
ll chaxun(ll l,ll r,ll p,ll nowl,ll nowr)//查询,原理与上面大致相同
{
	if(l<=nowl&&r>=nowr)return tree[p];
	pushdown(p,nowl,nowr);
	ll mid=(nowl+nowr)/2,a=0,b=0;
	if(l<=mid)a=chaxun(l,r,p*2,nowl,mid);
	if(mid<r)b=chaxun(l,r,p*2+1,mid+1,nowr);
	return a+b;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld",&zt);
		if(zt==0)
		{
			scanf("%lld%lld",&x,&y);
			addpoint2(x,y,1,1,n);
		}
		if(zt==1)
		{
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",chaxun(x,y,1,1,n));
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值