洛谷 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

将整个区间的数字取反。
那么01的数量将会调转。
所以区间1的总数就是,区间0的总数,即区间总长度减去1的总数。
因为01调转,所以本来的连续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]。
数字分别为1001110010
lx,ly分别为1,21,-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()
{
	sync
	cin>>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  
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值