结尾有彩蛋!看到最后!
题目描述
农夫约翰试图让奶牛玩智力玩具来保持它们的敏锐。谷仓里的灯是较大的玩具之一。
N ( 2 ≤ N ≤ 1 0 5 ) N (2 \le N \le 10^5) N(2≤N≤105) 个牛栏编号为 1 … N 1 \ldots N 1…N,每个牛栏上面都有一盏灯。起初所有的灯都关着。
共有 M M M 次操作,分为两种。
-
指定一个区间 [ S i , E i ] [S_i,E_i] [Si,Ei],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
-
指定一个区间 [ S i , E i ] [S_i,E_i] [Si,Ei],要求你输出这个区间内有多少盏灯是打开的。
题目分析
简单来说就是支持两个操作:区间异或 1 1 1 和区间加。
暴力做法
暴力修改 + 暴力查询。
时间复杂度 O ( n m ) O(nm) O(nm)。
代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m;
const int maxn=100100;
LL a[maxn];
int main()
{
// freopen("lites.in","r",stdin);
// freopen("lites.out","w",stdout);
scanf("%d%d",&n,&m);
while(m--)
{
int opt,l,r;
scanf("%d%d%d",&opt,&l,&r);
if(opt==0){
for(int i=l;i<=r;i++) a[i]^=1;
}
else{
int sum=0;
for(int i=l;i<=r;i++) sum+=a[i];
printf("%lld\n",sum);
}
}
return 0;
}
分数:JZYZ 题库 70 70 70 pts,洛谷 100 100 100 pts。
正解
线段树板子。
时间复杂度 O ( m log n ) O(m\log n) O(mlogn)。
分数:JZYZ 题库 100 100 100 pts,洛谷 100 100 100 pts。
定义
struct SegmentTree
{
int l,r;//左端点 右端点
LL data,add;//区间亮灯数 懒标记
}st[maxn*4];//数组开4倍
更新节点(upd 操作)
void upd(int p)
{
st[p].data=st[p<<1].data+st[p<<1|1].data;
//更新区间亮灯数为左区间亮灯数加上右区间亮灯数
}
下传懒标记
void spread(int p)
{
if(st[p].add)//存在懒标记
{
st[p<<1].data=st[p<<1].r-st[p<<1].l+1-st[p<<1].data;//区间亮灯数变为区间长度减去原来亮灯数
st[p<<1|1].data=st[p<<1|1].r-st[p<<1|1].l+1-st[p<<1|1].data;//同上
st[p<<1].add^=1;//懒标记更新 1变0 0变1
st[p<<1|1].add^=1;//同上
st[p].add=0;//将该点懒标记清空
}
}
建树。
和板子一样。
void build(int p,int l,int r)
{
st[p].l=l;st[p].r=r;st[p].add=0;
if(l==r)
{
st[p].data=0;//最初序列中全为0
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);//递归左子树
build(p<<1|1,mid+1,r);//递归右子树
upd(p);//更新节点
}
修改(change 操作)
void change(int p,int l,int r)
{
if(l<=st[p].l&&st[p].r<=r)//如果当前区间完全包含在修改区间中
{
st[p].data=st[p].r-st[p].l+1-st[p].data;//区间亮灯数变为区间长度减去原来亮灯数
st[p].add^=1;//懒标记异或
return;
}
spread(p);//下传懒标记
int mid=(st[p].l+st[p].r)>>1;
if(l<=mid) change(p<<1,l,r);//递归左子树
if(mid<r) change(p<<1|1,l,r);//递归右子树
upd(p);//更新节点
}
查询(ask 操作)
LL ask(int p,int l,int r)
{
if(l<=st[p].l&&st[p].r<=r)//如果当前区间完全包含在修改区间中
return st[p].data;
spread(p);//下传懒标记
int mid=(st[p].l+st[p].r)>>1;
LL sum=0;
if(l<=mid) sum+=ask(p<<1,l,r);//递归左子树
if(mid<r) sum+=ask(p<<1|1,l,r);//递归右子树
return sum;
}
主函数:
int main()
{
// freopen("lites.in","r",stdin);
// freopen("lites.out","w",stdout);
scanf("%d%d",&n,&m);
build(1,1,n);//建树
while(m--)
{
int opt,l,r;
scanf("%d%d%d",&opt,&l,&r);
if(opt==0) change(1,l,r);//区间异或
else printf("%lld\n",ask(1,l,r));//区间加
}
return 0;
}
完整 AC code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m;
const int maxn=100100;
LL a[maxn];
struct SegmentTree
{
int l,r;
LL data,add;
}st[maxn*4];
void spread(int p)
{
if(st[p].add)
{
st[p<<1].data=st[p<<1].r-st[p<<1].l+1-st[p<<1].data;
st[p<<1|1].data=st[p<<1|1].r-st[p<<1|1].l+1-st[p<<1|1].data;
st[p<<1].add^=1;
st[p<<1|1].add^=1;
st[p].add=0;
}
}
void upd(int p)
{
st[p].data=st[p<<1].data+st[p<<1|1].data;
}
void build(int p,int l,int r)
{
st[p].l=l;st[p].r=r;st[p].add=0;
if(l==r)
{
st[p].data=0;
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
upd(p);
}
LL ask(int p,int l,int r)
{
if(l<=st[p].l&&st[p].r<=r)
return st[p].data;
spread(p);
int mid=(st[p].l+st[p].r)>>1;
LL sum=0;
if(l<=mid) sum+=ask(p<<1,l,r);
if(mid<r) sum+=ask(p<<1|1,l,r);
return sum;
}
void change(int p,int l,int r)
{
if(l<=st[p].l&&st[p].r<=r)
{
st[p].data=st[p].r-st[p].l+1-st[p].data;
st[p].add^=1;
return;
}
spread(p);
int mid=(st[p].l+st[p].r)>>1;
if(l<=mid) change(p<<1,l,r);
if(mid<r) change(p<<1|1,l,r);
upd(p);
}
int main()
{
freopen("lites.in","r",stdin);
freopen("lites.out","w",stdout);
scanf("%d%d",&n,&m);
build(1,1,n);
while(m--)
{
int opt,l,r;
scanf("%d%d%d",&opt,&l,&r);
if(opt==0) change(1,l,r);
else printf("%lld\n",ask(1,l,r));
}
return 0;
}
彩蛋 该题在洛谷有五倍经验:分别为 P2846 P3870 P2574 P5057 SP7259
关注 zhujiangyuan 谢谢喵。