P6492 [COCI2010-2011#6] STEP

题目描述

给定一个长度为 n 的字符序列 a,初始时序列中全部都是字符 L。
有q 次修改,每次给定一个 x,若 ax​为 L,则将 ax​修改成 R,否则将 ax​修改成 L。
对于一个只含字符 L,R 的字符串 s,若其中不存在连续的 L 和 R,则称 s 满足要求。
每次修改后,请输出当前序列 a 中最长的满足要求的连续子串的长度。

输入格式

第一行有两个整数,分别表示序列的长度 n 和修改操作的次数 q。
接下来 q 行,每行一个整数,表示本次修改的位置 x。

输出格式

对于每次修改操作,输出一行一个整数表示修改 a 中最长的满足要求的子串的长度。

样例

输入
6 2
2
4
输出
3
5
输入
6 5
4
1
1
2
6
输出
3
3
3
5
6

说明/提示

数据规模与约定
对于全部的测试点,保证 1≤n,q≤2×10 5 ,1≤x≤n。

题目分析

这是一道线段树维护最大子段和的变形题目,(不会用线段树维护最大子段和的可以先看这个题:你能回答这些问题吗

因为这个题的序列只有L和R两种字符,因此我们可以直接将L改为1,R改为0。从而将LR序列改为了一个01序列,这样只需要用线段树维护一个01序列即可。
线段树内部维护的信息即为:0101这样的交替子序列的最长长度。这样的交替子序列的计算方法其实是和最大子段和的求法几乎是一样的。
max为线段树这一段中交替序列的最长长度
lmax为线段树这一段中交替序列的最长前缀长度
rmax为线段树这一段中交替序列的最长后缀长度
len为这一段的长度
l 这一段中最左边的数字(0/1)
r 这一段中最右边的数字(0/1)
(与正常的线段树不同,原线段树中维护区间左右端点的l,r放到函数中完成)
这几个变量的求法和最大子段和问题中几个辅助信息是一样的。我们只说不一样的地方:在pushup操作(通过左右两子段求得父段信息)中,当父段的某个信息的某种求法涉及到两个子段时,我们需要先判断两个子段是否能够拼接到一起去。(即左子段的r和右子段的l是否不相等)只有即左子段的r和右子段的l不相等,父段才能利用两字段信息拼接的结果。
例如:tr[u].max=max(max(tr[u<<1].max,tr[u<<1|1].max),tr[u<<1].rmax+tr[u<<1|1].lmax]) 最后的左子段的最大后缀+右子段的最大前缀 的这个操作只有在tr[u<<1].r!=tr[u<<1|1].l 的时候才能用。

剩下的修改和查询就都是基础操作了,不多说了。

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <stack>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <iomanip>
#define LL long long
#define PII pair<int,int>
using namespace std;
const int N=2e5+5;
struct Node{
	int l,r;				//线段树维护的6个辅助信息(前面都讲了)
	int max,lmax,rmax,len;
}tr[N*4];
int n,m;
void pushup(int u)			//通过子段求得父段信息
{
	Node &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	
	root.l=left.l;			//这些都是不需要两子段进行拼接就可以做到的
	root.r=right.r;
	root.lmax=left.lmax;
	root.rmax=right.rmax;
	root.max=max(left.max,right.max);
	if(left.r!=right.l)		//如果可以进行拼接,再加上两子段拼接可以得到的信息
	{
		root.max=max(root.max,left.rmax+right.lmax);
		if(left.max==left.len)
		{	//如果左子段全部为交替序列(且可以进行拼接),那么父段的最大前缀和即为左子段全部+右子段的最大前缀
			root.lmax=left.max+right.lmax;
		}
		if(right.max==right.len)		//同理
		{
			root.rmax=right.max+left.rmax; 
		}
	}
}
void build(int u,int l,int r)	//建树
{
    tr[u]={1,1,1,1,1};			//因为初始全为L,因此l,r初始都为1
    tr[u].len=r-l+1;
	if(l==r) return;
	else
	{
		int mid=l+r>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void update(int u,int x,int l,int r)	//单点修改(模板)
{
	if(l==x&&r==x)
	{
		tr[u].l^=1;			//如果是0变为1
		tr[u].r^=1;			//如果是1变为0
	}
	else
	{
		int mid=l+r>>1;
		if(x<=mid) update(u<<1,x,l,mid);
		else update(u<<1|1,x,mid+1,r);
		pushup(u);
	}
}
int query()			//因为每次查询只是查询全局的情况,因此可以只看tr[1]
{
	return max(tr[1].max,max(tr[1].lmax,tr[1].rmax));
}
int main()
{
	scanf("%d %d",&n,&m);
	build(1,1,n);
	while(m--)
	{
		int x;
		scanf("%d",&x);
		update(1,x,1,n);
		printf("%d\n",query());
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值