洛谷 P2572 [SCOI2010] 序列操作 题解(线段树)
前言
P2572这道题真的相当折磨,相当难调了。希望这一篇题解可以帮到你。如果畏惧了超巨的码量请移步神仙压行。
本题解已尽最大努力肢解整一份代码,所以比较长。
线段树的构成
num[N],lazy[N]; //1的数量 num1[N];//连续1的数量 num0[N];//连续0的数量 lx[N],ly[N];//当前区间最左侧和最右侧的1/0连续数量(正数/负数)
lx,ly函数用于判断两个区间的1能否合并。正数表示连续1的数量,负数表示连续0的数量。
操作0~2的change函数
操作0
将整个区间直接赋值为0
。
那么区间1的数量和连续1的数量均变成0
区间连续0的数量为区间总长度。
当前最左、最右的0连续数量也为区间总长度,但是由于是0的连续数量所以值为负数。
num[id]=0;num1[id]=0;
num0[id]=r-l+1;lx[id]=ly[id]=-(r-l+1);//0是负数lazy[id]=z; //lazy标记
操作1
将整个区间直接赋值为1
。
那么区间1的数量和连续1的数量均变成区间总长度
区间连续0的数量为0
。
当前最左、最右的1连续数量也为区间总长度,但是由于是1的连续数量所以值为正数。
num[id]=(r-l+1);num1[id]=r-l+1;
num0[id]=0;lx[id]=ly[id]=r-l+1;lazy[id]=z;
操作2
将整个区间的数字取反。
那么0
和1
的数量将会调转。
所以区间1
的总数就是,区间0
的总数,即区间总长度减去1
的总数。
因为0
和1
调转,所以本来的连续0
变成了1
,1
变成0
。
num[id]=r-l+1-num[id];swap(num1[id],num0[id]);//取反的话 最长连续1 和 最长连续0 正好调转lx[id]*=-1;//取反的话 连续长度 但是正负要变
ly[id]*=-1;
关于操作2的lazy
在进行取反操作前如果有赋值操作那么要先进行赋值操作。
比如之前全赋值为1
取反之后相当于全赋值为0
。
进行取反的lazy标记时记得取反是可以叠加的。两次取反操作等于没操作。
if(lazy[id]==1)
{lazy[id]=2;
}
else if(lazy[id]==2)
{lazy[id]=1;
}
else
{lazy[id]+=z;//取反状态不是可重复的是叠加的
}
查询3的query函数
简单的求和函数。
代码就不放了
查询4的ans函数
查询连续1
的数量时不能直接查询求max
。需要考虑拼接的情况。所以需要下面两个区间的lx
ly
的值。所以我们可以返回直接一个结构体。
下放函数pushdown
操作原理是和change函数一样的,不再赘述。
pushup函数
pushup函数关键是要考虑清楚两个区间如何合并。
我们举几个例子就好了。
比如合并区间[1,5]和[6,10]。
数字分别为10011
和10010
。
lx
,ly
分别为1,2
和1,-1
。
判断能否拼接只需要左半部分的最右侧(ly),和右半部分的最左侧(lx)是否同号即可。
那么大区间的lx
继承左半部分的lx
,ly
继承右半部分ly
。
当然会有一种特殊情况。如果某半边全是1
或者0
。那么lx
ly
不应仅是继承,应该是合并。
代码
#include<bits/stdc++.h>
#include<cstring>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#include<map>
#define ll long long
#define lhs printf("\n");
#define sync std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0);
using namespace std;
const int N=3e6+10;
const int M=2021;
const int inf=0x3f3f3f3f;
int n,m;
int a[N];
int num[N],lazy[N]; //1的数量
int num1[N];//连续1的数量
int num0[N];//连续0的数量
int lx[N],ly[N];//当前区间最左侧和最右侧的1/0连续数量(正数/负数)
struct node//用于查询连续1的数量时
{int lx=0,ly=0,num=0;
};
void pushup(int id,int l,int r)
{int mid=(l+r)/2;num[id]=num[id*2]+num[id*2+1];if(ly[id*2]>0 and lx[id*2+1]>0)//1可以拼起来{num1[id]=max(num1[id*2],max(num1[id*2+1],ly[id*2]+lx[id*2+1])); } else{num1[id]=max(num1[id*2],num1[id*2+1]);}if(ly[id*2]<0 and lx[id*2+1]<0)//0可以拼起来{num0[id]=max(num0[id*2],max(num0[id*2+1],-ly[id*2]-lx[id*2+1])); } else{num0[id]=max(num0[id*2],num0[id*2+1]);}if(lx[id*2]==mid-l+1 and lx[id*2+1]>0)//左下满了1 且 右下最左是1 那么拼在一起 {lx[id]=lx[id*2]+lx[id*2+1];} else if(lx[id*2]==-(mid-l+1) and lx[id*2+1]<0)//左下满了0 且 右下最左是0 那么拼在一起 {lx[id]=lx[id*2]+lx[id*2+1];} else{lx[id]=lx[id*2];}if(ly[id*2+1]==r-mid and ly[id*2]>0) //右下满了1 且 左下最有也是1 {ly[id]=ly[id*2+1]+ly[id*2];}else if(ly[id*2+1]==-(r-mid) and ly[id*2]<0)//右下全是0 且 左下最右也是0 {ly[id]=ly[id*2+1]+ly[id*2]; } else{ly[id]=ly[id*2+1];}
}
void pushdown(int id,int l,int r)
{if(lazy[id]){int mid=(l+r)/2;if(lazy[id]==1)//全变0 {num[id*2]=0;num0[id*2]=mid-l+1;num1[id*2]=0; lx[id*2]=ly[id*2]=-(mid-l+1); lazy[id*2]=1;num[id*2+1]=0;num0[id*2+1]=r-mid; num1[id*2+1]=0;lx[id*2+1]=ly[id*2+1]=-(r-mid); lazy[id*2+1]=1 ; }else if(lazy[id]==2)//全变1 {num[id*2]=mid-l+1;num0[id*2]=0;num1[id*2]=mid-l+1; lx[id*2]=ly[id*2]=(mid-l+1); lazy[id*2]=2;num[id*2+1]=r-mid;num0[id*2+1]=0;num1[id*2+1]=r-mid;lx[id*2+1]=ly[id*2+1]=(r-mid); lazy[id*2+1]=2;}else if(lazy[id]%3==0 and lazy[id]%2==1)//取反 {num[id*2]=mid-l+1-num[id*2];swap(num0[id*2],num1[id*2]);lx[id*2]*=-1;ly[id*2]*=-1; if(lazy[id*2]==1)//需要先进行lazy的赋值下放操作 {lazy[id*2]=2;}else if(lazy[id*2]==2){lazy[id*2]=1;}else{lazy[id*2]+=3;}num[id*2+1]=r-mid-num[id*2+1];swap(num0[id*2+1],num1[id*2+1]);lx[id*2+1]*=-1;ly[id*2+1]*=-1; if(lazy[id*2+1]==1)//需要先进行lazy的赋值下放操作 {lazy[id*2+1]=2;}else if(lazy[id*2+1]==2){lazy[id*2+1]=1;}else{lazy[id*2+1]+=3;}}lazy[id]=0; }
}
void build(int id,int l,int r)
{if(l==r){num[id]=a[l];if(a[l])//1{num1[id]=1;lx[id]=ly[id]=1;}else//0{num0[id]=1;lx[id]=ly[id]=-1;} return; } int mid=(l+r)/2;build(id*2,l,mid);build(id*2+1,mid+1,r);pushup(id,l,r);
}
void change(int id,int l,int r,int x,int y,int z)
{if(x<=l and r<=y){if(z==1)//0{num[id]=0;num1[id]=0;num0[id]=r-l+1;lx[id]=ly[id]=-(r-l+1);lazy[id]=z; } else if(z==2)//1{num[id]=(r-l+1);num1[id]=r-l+1;num0[id]=0;lx[id]=ly[id]=r-l+1;lazy[id]=z; }else//取反 {num[id]=r-l+1-num[id];swap(num1[id],num0[id]);//取反的话 最长连续1 和 最长连续0 正好调转lx[id]*=-1;//取反的话 连续长度 但是正负要变 ly[id]*=-1; if(lazy[id]==1){lazy[id]=2;}else if(lazy[id]==2){lazy[id]=1;}else{lazy[id]+=z;//取反状态不是可重复的是叠加的}}return;} pushdown(id,l,r); int mid=(l+r)/2;if(x<=mid){change(id*2,l,mid,x,y,z);} if(mid+1<=y){change(id*2+1,mid+1,r,x,y,z);}pushup(id,l,r);
} int query(int id,int l,int r,int x,int y)//1数量
{if(x<=l and r<=y){return num[id];}pushdown(id,l,r);int mid=(l+r)/2;int sum=0;if(x<=mid){sum+=query(id*2,l,mid,x,y); }if(mid+1<=y){sum+=query(id*2+1,mid+1,r,x,y);}return sum;
}
node ans(int id,int l,int r,int x,int y)//连续1数量
{if(x<=l and r<=y){int aa=lx[id],bb=ly[id],cc=num1[id];node xx;xx.lx=aa;xx.ly=bb;xx.num=cc;return xx;} pushdown(id,l,r);int mid=(l+r)/2;int maxx=0;node xx,yy;int flag1=0,flag2=0;if(x<=mid){xx=ans(id*2,l,mid,x,y);flag1=1;}if(mid+1<=y){yy=ans(id*2+1,mid+1,r,x,y);flag2=1;}int lxx,lyy;if(x<=mid and mid+1<=y and xx.ly>0 and yy.lx>0) //1可以拼起来 {if(xx.lx==mid-l+1 and yy.lx>0)//左下满了1 且 右下最左是1 那么拼在一起 {lxx=xx.lx+yy.lx;} else{lxx=xx.lx;}if(yy.ly==r-mid and xx.ly>0) //右下满了1 且 左下最有也是1 {lyy=xx.ly+yy.ly;}else{lyy=yy.ly;} int aa=lxx,bb=lyy,cc=max(xx.num,max(yy.num,xx.ly+yy.lx));node s;s.lx=aa;s.ly=bb;s.num=cc;return s; }if(flag1 and flag2)//两边都访问到了那么取大的那边 {int aa=xx.lx,bb=yy.ly,cc=max(xx.num,yy.num);node s;s.lx=aa;s.ly=bb;s.num=cc;return s;}else//否则只取一边 {if(flag1){return xx; } else{return yy;}}
}
int main()
{synccin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];build(1,1,n); for(int i=1;i<=m;i++){int op,x,y;cin>>op>>x>>y;x++;y++; if(op==0){change(1,1,n,x,y,op+1); }else if(op==1){change(1,1,n,x,y,op+1);}else if(op==2){change(1,1,n,x,y,op+1);}else if(op==3){cout<<query(1,1,n,x,y)<<"\n"; } else{cout<<ans(1,1,n,x,y).num<<"\n";}}return 0;
}
/*
10 10
1 0 0 0 1 0 1 1 0 1
0 6 8
1 0 8
2 1 7
2 6 6
4 6 8
1 6 6
4 6 7
0 0 6
1 5 7
4 9 9答案:1 1 1
*/