COCI2010/2011 踢踏舞 线段树区间子段和

无题目传送门

大致题意:

有一初始为\(0\),长度为\(n\)的序列。\(q\)次操作反转单点(01互换)。每次操作后求最长的\(01\)间隔排列的子段长度。

\(1\leq n,q \leq 200000\)


Solution

直接求\(01\)的序列长是比较困难的(指蒟蒻用珂朵莉疯狂TLE),所以考虑转化。

因为是\(01\)间隔的,所以把它们每相邻两个异或起来就会得到一个全\(1\)且长度小\(1\)的序列。

比如\(010100\)就会变成\(11110\)

由于是单点修改,所以每次仅会影响对应异或序列中的前后元素。

所以可以方便地把问题转化为,维护一个长度为\(n-1\)的序列,每次对\(x\)的操作改为对\(x\)\(x+1\)的取反(如果不为\(1\)\(n\))。这在原序列中表示的是\((x,x-1)\)\((x,x+1)\)的异或值。

然后最后的问题就是解决在这样一个序列中求最长的连续\(1\)的个数了。

由于是动态的,维护序列自然想到线段树。最长连续\(1\)的个数可以换一个思维,也就是不含0的最大子段和

那么怎么做到不含0呢?只需要让我们求最大子段和取不到0就行了。那么把原有的0都换成极小值,再用线段树维护一个区间子段和就可以了。

简单说下求区间子段和。

一个线段树节点维护四个信息:\(l,r,v,s\),分别表示以左端点开头的最大子段和,以右端点结束的最大子段和,节点内最大子段和,节点内权值和

每次都是单点修改,那么修改后的节点\(k\)四个参数分别应该为0,0,0,-INF(修改为负值)或1,1,1,1(修改为正值)。

核心:

t[k].s=t[ls].s+t[rs].s;
t[k].l=max(t[ls].l,t[ls].s+t[rs].l);
t[k].r=max(t[rs].r,t[rs].s+t[ls].r);
t[k].v=max(max(t[ls].v,t[rs].v),t[ls].r+t[rs].l);

从左端点开头的最大子段和,要么是他左儿子的最大子段和,要么是他左儿子的权值和再加上右儿子从左端开始的最大子段和。这样是两种保证子段连续的情况。右端点结尾的同理。

整体的最大子段和要么是左/右儿子的最大子段和,要不是左右儿子各出自己的右部分和左部分拼在一起。

于是就完了


code:

#include<bits/stdc++.h>
#define int long long
#define N 200020
#define INF 0x3f3f3f3f
#define ls (k<<1)
#define rs (k<<1|1)
using namespace std;
struct node
{
	int l,r,v,s;
}t[N<<2];
int val[N];
void work(int k,int l,int r,int p,int c)
{
	if(l==r)
	{
		if(c==1)t[k]=(node){1,1,1,1};
		else t[k]=(node){0,0,0,c};
		return;
	}
	int m=l+r>>1;
	if(p<=m)work(ls,l,m,p,c);
	else work(rs,m+1,r,p,c);
	t[k].s=t[ls].s+t[rs].s;
	t[k].l=max(t[ls].l,t[ls].s+t[rs].l);
	t[k].r=max(t[rs].r,t[rs].s+t[ls].r);
	t[k].v=max(max(t[ls].v,t[rs].v),t[ls].r+t[rs].l);
}
void build(int l,int r,int k)
{
	if(l==r)
	{
		t[k].s=-INF;
		return;
	}
	int m=l+r>>1;
	build(l,m,ls);
	build(m+1,r,rs);
	t[k].s=t[ls].s+t[rs].s;
}
signed main()
{
	int n,m,x;
	scanf("%lld%lld",&n,&m);
	build(1,n,1);
	while(m--)
	{
		scanf("%lld",&x);
		val[x]=(!val[x]);
		if(x!=1)work(1,1,n,x  ,val[x]^val[x-1]?1:-INF);
		if(x!=n)work(1,1,n,x+1,val[x]^val[x+1]?1:-INF);
		printf("%lld\n",t[1].v+1);
	}
}
posted @ 2020-09-29 21:42  摸鱼酱  阅读(176)  评论(0编辑  收藏  举报