原题链接

前置知识:

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

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

  1. 区间取反;

  2. 区间求和。

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

典型的 线段树

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

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

\(a_i \in {0,1}\)

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

\(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\n",query(1,l,r));
}
return 0;
}
05-11 19:21