分块
题目大意
给出一个长度为 n n n 的序列,有区间取反,和区间求和操作。输出每一次求和询问的答案
这应该算的上是一道模板的分块题了,写完后感觉分块比线段树简单好多
首先,我们将这个序列分成
n
\sqrt{n}
n 块,保证最优化复杂的
维护四个数组
a
[
]
,
s
u
m
[
]
,
t
a
g
[
]
,
i
d
[
]
a[],sum[],tag[],id[]
a[],sum[],tag[],id[] 分别表示每盏灯的状态,每个块中开着的灯数总和,每块是否取反,和每盏灯属于的块的编号
每一次区间 询问/修改,对于不整块的部分,直接修改它的
a
[
]
a[]
a[] 数组
对于整块的,我们直接打上标记并更
s
u
m
[
]
sum[]
sum[] 数组
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int Maxn=100000+10,inf=0x3f3f3f3f;
int n,m,len;
int id[Maxn],sum[Maxn],a[Maxn],tag[Maxn];
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
void modify(int l,int r)
{
int limit=min(id[l]*len,r);
for(int i=l;i<=limit;++i)
{
sum[id[i]]-=(a[i]^tag[id[i]]); //减去之前他对所在的块做的贡献
a[i]^=1; // 取反
sum[id[i]]+=(a[i]^tag[id[i]]); // 加上取反后对所在的块做的贡献
}
if(id[l]!=id[r])
{
for(int i=(id[r]-1)*len+1;i<=r;++i)
{
sum[id[i]]-=(a[i]^tag[id[i]]);
a[i]^=1;
sum[id[i]]+=(a[i]^tag[id[i]]);
}
}
for(int i=id[l]+1;i<=id[r]-1;++i)
{
tag[i]^=1;
sum[i]=len-sum[i];
}
}
int query(int l,int r) // 与上同理
{
int limit=min(id[l]*len,r);
int ret=0;
for(int i=l;i<=limit;++i)
ret+=(a[i]^tag[id[i]]);
if(id[l]!=id[r])
{
for(int i=(id[r]-1)*len+1;i<=r;++i)
ret+=(a[i]^tag[id[i]]);
}
for(int i=id[l]+1;i<=id[r]-1;++i)
ret+=sum[i];
return ret;
}
int main()
{
//freopen("in.txt","r",stdin);
n=read(),m=read();
len=sqrt(n);
for(int i=1;i<=n;++i)
id[i]=(i-1)/len+1;
for(int i=1;i<=m;++i)
{
bool opt=read();
int l=read(),r=read();
if(!opt)modify(l,r);
else printf("%d\n",query(l,r));
}
return 0;
}