洛谷 P3870 [TJOI2009]开关 题解

原题链接

前置知识:

线段树的单点、区间的修改与查询。

一看,我们需要维护两个操作:

  1. 区间取反;

  2. 区间求和。

(因为区间 (1) 的个数,就是区间的和)

典型的 线段树

如果你只会线段树的 区间修改,单点修改,区间查询,单点查询 的话,这题的 “取反” 是个难题。

但是,这个数组有个性质:

(a_i in {0,1})

也就是说,假设一个数组一开始这样子:

(1) (2) (3) (4)
(a_i) (0) (1) (0) (0)
(b_i) (1) (0) (1) (1)

翻转过后,你会发现:

翻转后的区间和 = 区间长度 - 区间和。

因为 原来的区间和(1) 的个数,减掉 (1) 的个数就是 (0) 的个数,而 (0) 翻转后就是 (1),会对答案产生贡献。

下面区间翻转的标记叠加怎么办?

显然,翻转 偶数 次直接变成 (0),因为等于没有翻转;翻转 奇数 次变成 (1),因为等于翻转 (1) 次。

那么,每次翻转在标记上 异或 一下就行。

(异或之后,(0 gets 1)(1 gets 0)

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+1;
#define L (i<<1)
#define R (i<<1)+1

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

struct tree{
	int l,r,sumi;
	int tag;
};
tree t[N<<2];
int n,m;

inline void update(int i) {
	t[i].sumi=t[L].sumi+t[R].sumi;
}

inline void build_tree(int i,int l,int r) {
	t[i].l=l; t[i].r=r; t[i].tag=0; t[i].sumi=0;
	if(l==r) return; int mid=(l+r)>>1;
	build_tree(L,l,mid);
	build_tree(R,mid+1,r); 
} //建树

inline void pushdown(int i) {
	int x=t[i].tag; if(!x) return;
	t[L].sumi=t[L].r-t[L].l+1-t[L].sumi;
	t[R].sumi=t[R].r-t[R].l+1-t[R].sumi;
	t[L].tag^=1; t[R].tag^=1; t[i].tag=0;
} //下传标记

inline void change(int i,int l,int r) {
	if(l<=t[i].l && t[i].r<=r) {
		t[i].sumi=t[i].r-t[i].l+1-t[i].sumi;
		t[i].tag^=1; return;
	} pushdown(i); int mid=(t[i].l+t[i].r)>>1;
	if(l<=mid) change(L,l,r);
	if(r>mid) change(R,l,r);;
	update(i);
} //区间修改

inline int query(int i,int l,int r) {
	if(l<=t[i].l && t[i].r<=r) return t[i].sumi;
	pushdown(i); int mid=(t[i].l+t[i].r)>>1,ans=0;
	if(l<=mid) ans+=query(L,l,r);
	if(r>mid) ans+=query(R,l,r);
	return ans;
} //区间询问

int main(){
	n=read(); m=read();
	build_tree(1,1,n);
	while(m--) {
		int opt=read(),l=read(),r=read();
		if(!opt) change(1,l,r);
		else printf("%d
",query(1,l,r));
	}
	return 0;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12515374.html