[BZOJ1858]SCOI2010序列操作|线段树

一看以为是线段树水题,写起来发现好像询问4比较麻烦,调了好久。。

对每个线段树节点维护sum为区间内1的个数,ml0mr0表示左右的最长连续0长度,ml1mr1同理,m0m1为区间内连续0/1的最大长度,合并信息的时候用ml0mr0ml1mr1来更新m0m1,就和维护数列那题一样。。这里要注意合并时维护ml1mr1ml0mr0的时候要考虑能不能延长到另半个区间去,就像下图一样:

   修改的时候打标记就是了,注意的地方就是如果有全0/1的标记打上去其他原来的标记都要清空。。

询问3好说,我就不讲了。。询问4比较麻烦,要将答案作为一个线段树节点一样上传,然后像线段树节点合并一样合并,其实也好搞。。

#include<cstdio>
#include<iostream>
#define N 400005
using namespace std;
int n,m,i,nd=0,qd,root,opt,L,R,a[N],c[N][2],l[N],r[N],ml0[N],ml1[N],mr0[N],mr1[N],m1[N],m0[N],sum[N],tag0[N],tag1[N],rev[N];
void update(int x)
{
	int lc=c[x][0],rc=c[x][1];
	ml0[x]=ml0[lc];ml1[x]=ml1[lc];
	mr0[x]=mr0[rc];mr1[x]=mr1[rc];
	if (ml0[lc]==r[lc]-l[lc]+1) ml0[x]=ml0[lc]+ml0[rc];
	if (ml1[lc]==r[lc]-l[lc]+1) ml1[x]=ml1[lc]+ml1[rc];
	if (mr0[rc]==r[rc]-l[rc]+1) mr0[x]=mr0[rc]+mr0[lc];
	if (mr1[rc]==r[rc]-l[rc]+1) mr1[x]=mr1[rc]+mr1[lc];
	m1[x]=max(mr1[lc]+ml1[rc],max(m1[lc],m1[rc]));
	m0[x]=max(mr0[lc]+ml0[rc],max(m0[lc],m0[rc]));
	sum[x]=sum[lc]+sum[rc];
}
void mark0(int x)
{
	tag0[x]=1;tag1[x]=0;rev[x]=0;
	ml0[x]=mr0[x]=m0[x]=r[x]-l[x]+1;
	ml1[x]=mr1[x]=m1[x]=sum[x]=0;
}
void mark1(int x)
{
	tag1[x]=1;tag0[x]=0;rev[x]=0;
	ml0[x]=mr0[x]=m0[x]=0;
	ml1[x]=mr1[x]=m1[x]=sum[x]=r[x]-l[x]+1;
}
void markr(int x)
{
	rev[x]^=1;
	swap(ml0[x],ml1[x]);swap(mr0[x],mr1[x]);
	swap(m0[x],m1[x]);
	sum[x]=r[x]-l[x]+1-sum[x];
}
void down(int x)
{
	if (tag0[x]) mark0(c[x][0]),mark0(c[x][1]),tag0[x]=0;
	if (tag1[x]) mark1(c[x][0]),mark1(c[x][1]),tag1[x]=0;
	if (rev[x]) markr(c[x][0]),markr(c[x][1]),rev[x]=0;
}
void build(int &x,int ll,int rr)
{
	x=++nd;
	l[x]=ll;r[x]=rr;
	tag0[x]=tag1[x]=rev[x]=0;
	if (ll==rr)
	{
		c[x][0]=c[x][1]=0;
		ml0[x]=mr0[x]=m0[x]=(a[ll]==0);
		ml1[x]=mr1[x]=m1[x]=sum[x]=(a[ll]==1);
		return;
	}
	int mid=(ll+rr)/2;
	build(c[x][0],ll,mid);build(c[x][1],mid+1,rr);
	update(x);
}
void change(int x,int ll,int rr,int k)
{
	if (l[x]>rr||r[x]<ll) return;
	down(x);
	if (ll<=l[x]&&rr>=r[x]) 
	{
		if (k==0) mark0(x);else if (k==1) mark1(x);else markr(x);
		return;
	}
	change(c[x][0],ll,rr,k);change(c[x][1],ll,rr,k);
	update(x);
}
int query(int x,int ll,int rr)
{
	if (l[x]>rr||r[x]<ll) return 0; 
	down(x);
	if (ll<=l[x]&&rr>=r[x]) return sum[x];
	return query(c[x][0],ll,rr)+query(c[x][1],ll,rr);
}
int query1(int x,int ll,int rr)
{
	down(x);
	if (ll==l[x]&&rr==r[x])	return x;
	int mid=(l[x]+r[x])/2;
	if (rr<=mid) return query1(c[x][0],ll,rr);
	else if (ll>mid) return query1(c[x][1],ll,rr);
	else
	{
		int ans=++qd;
		c[ans][0]=query1(c[x][0],ll,mid);c[ans][1]=query1(c[x][1],mid+1,rr);
		update(ans);
		return ans;
	}
}
int main()
{
	freopen("1858.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	build(root,1,n);
	for (i=1;i<=m;i++)
	{
		scanf("%d%d%d",&opt,&L,&R);
		if (opt==0||opt==1||opt==2) change(root,L+1,R+1,opt);
		else
		{
			if (opt==3) printf("%d\n",query(root,L+1,R+1)); else qd=nd,printf("%d\n",m1[query1(root,L+1,R+1)]);
		}
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值