乱搞



Solution

题目大意:01 序列区间取反,区间求和

维护一个 01 序列,可以想到用 \(\mathsf{bitset}\),而其自带的位运算与 ​​count​​ 函数恰好支持这两个操作。那用什么位运算呢——异或。

我们知道,异或操作就是相同为 \(0\),不同为 \(1\),所以把一段序列异或上一整段全是 \(1\) 的序列就可以把它取反。那么难点就在于造出这个一段全是 \(1\) 的序列。

可以考虑用前缀和,首先预处理 \(s_i\),即前 \(i\) 位全是 \(1\) 的 \(\mathsf{bitset}\),那 \([l,r]\) 就是 \(s_r\;{\rm{xor}}\;s_{l-1}\)。但本题只有 125MB 的空间,存不下 \(n\) 个 \(\mathsf{bitset}\),那么可以用分段打表的思想,预处理时每 \(20\) 个存一个 \(s_i\),再直接位运算调整到 \(l,r\) 即可。这样的复杂度为 \(\mathcal O(\dfrac{nm}{\omega})\),\(\omega\) 为计算机位长,稍加卡常可过。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,tot;
bitset<N>bs,a(1),s[N>>4],b(1),s1,s2;
void make(int l,int r){
int bl=l/20,br=(r-1)/20+1;
s1=s[bl],s2=s[br];
s2>>=(br*20-r);
s1<<=(l-bl*20);s1|=(1<<(l-bl*20+1))-1;
a=s1^s2;
}
int main(){
scanf("%d%d",&n,&m);s[0]=a;
for(int i=1;i<=n+20;i++){
a=a<<1|b;
if(i%20==0)s[++tot]=a;
}
for(int i=1,op,l,r;i<=m;i++){
scanf("%d%d%d",&op,&l,&r);
make(l-1,r);
if(!op)bs^=a;
else a&=bs,printf("%d\n",a.count());
}
return 0;
}