bzoj 2209: [Jsoi2011]括号序列(splay)

2209: [Jsoi2011]括号序列

Time Limit: 20 Sec   Memory Limit: 259 MB
Submit: 1082   Solved: 526
[ Submit][ Status][ Discuss]

Description

Input

输入数据的第一行包含两个整数N和Q,分别表示括号序列的长度,以及操作的个数。 第二行包含一个长度为N的括号序列。 接下来Q行,每行三个整数t、x和y,分别表示操作的类型、操作的开始位置和操作的结 束位置,输入数据保证x不小于y。其中t=0表示询问操作、t=1表示反转操作、t=2表示翻转操 作。

Output

对于每一个询问操作,输出一行,表示将括号序列的该子序列修改为配对,所需的最少改动 个数。

Sample Input

6 3
)(())(
0 1 6
0 1 4
0 3 4

Sample Output

2
2
0

HINT

100%的数据满足N,Q不超过10^5

Source

[ Submit][ Status][ Discuss]


题解:splay 

我们将括号序列改成-1与1的序列,用-1表示(,用1表示)。

那么一段区间中前缀和的最大值就是未匹配的)的数量(prex表示),后缀和的最小值为未匹配的(的数量(sufn表示)。

如果未匹配的(,)都是偶数的话,那答案就是(prex+sufn)/2

如果未匹配的(,)都是奇数的话,那答案就是(prex+sufn)/2+1 (至于为什么,画一画括号找找规律啦)

翻转操作: 交换左右子树,打翻转标记,直接交换pre 和 suf 数组即可,因为以前的前缀变成了后缀,数值并未发生改变。

反转操作:打反转标记,将pren,prex,sufn,sufx,sum,key取反,swap(pren,prex),swap(sufn,sufx)。因为反转之后-1,与1,都会发生变化,而且取反后最大值与最小值都会发生大小交换。

有两个标记会不会发生标记冲突呢?这道题比较好,通过手动操作可以发现两个标记互不影响,先下放哪一个的效果都是相同的。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 500003
using namespace std;
int n,m,fa[N],size[N],sum[N],key[N],ch[N][3],prefx[N],prefn[N],sufx[N],sufn[N];
int pos[N],root,neg[N],rev[N],cnt;
char s[N];
void update(int now)//前缀和的最大值是多出的)的数量(最大值应该大于等于0),后缀和的最小值是多出的( 的数量(最小值应该小于等于0) 
{
	prefx[0]=sufx[0]=-1000000000; 
	prefn[0]=sufn[0]=1000000000; key[0]=0;
	int l=ch[now][0]; int r=ch[now][1];
	size[now]=size[l]+size[r]+1;
	sum[now]=sum[l]+sum[r]+key[now];
	int lenp=max(sum[l]+key[now],sum[l]+prefx[r]+key[now]);
	prefx[now]=max(lenp,prefx[l]);
	lenp=min(sum[l]+key[now],sum[l]+prefn[r]+key[now]);
	prefn[now]=min(lenp,prefn[l]);
	int lens=max(sum[r]+key[now],sum[r]+sufx[l]+key[now]);
	sufx[now]=max(lens,sufx[r]);
	lens=min(sum[r]+key[now],sum[r]+sufn[l]+key[now]);
	sufn[now]=min(lens,sufn[r]);
}
void change(int now)
{
	swap(prefx[now],sufx[now]);
	swap(prefn[now],sufn[now]);
}
void change1(int now)
{
	sum[now]=-sum[now]; key[now]=-key[now];
	prefx[now]=-prefx[now]; sufx[now]=-sufx[now];
	prefn[now]=-prefn[now]; sufn[now]=-sufn[now];
	swap(prefx[now],prefn[now]);
	swap(sufn[now],sufx[now]);
}
void pushdown(int x)
{
	if (rev[x])
	 {
	 	change(ch[x][0]); change(ch[x][1]);
	 	swap(ch[x][0],ch[x][1]);
	 	rev[ch[x][0]]^=1; rev[ch[x][1]]^=1;
	 	rev[x]=0;
	 }
	if (neg[x])
	 {
	 	change1(ch[x][0]); change1(ch[x][1]);
	 	neg[ch[x][0]]^=1; neg[ch[x][1]]^=1;
	 	neg[x]=0;
	 }
}
int build(int l,int r,int f)
{
	if (l>r) return 0;
	int mid=(l+r)/2; int now=++cnt;
	key[now]=pos[mid];  fa[now]=f;
	ch[now][0]=build(l,mid-1,now);
	ch[now][1]=build(mid+1,r,now);
	update(now);
	//cout<<l<<" "<<r<<" "<<now<<"!"<<endl;
//	cout<<prefx[now]<<" "<<prefn[now]<<" "<<sufx[now]<<" "<<sufn[now]<<" "<<sum[now]<<endl;
	return now;
}
int get(int x)
{
	return ch[fa[x]][1]==x;
}
void rotate(int x)
{
	int y=fa[x]; int z=fa[y]; 
	pushdown(y); pushdown(x); int which=get(x);
	ch[y][which]=ch[x][which^1]; fa[ch[x][which^1]]=y;
	ch[x][which^1]=y; fa[y]=x; fa[x]=z;
	if (z) ch[z][ch[z][1]==y]=x;
	update(y); update(x); 
}
void splay(int x,int tar)
{
	for (int f;(f=fa[x])!=tar;rotate(x))
	 if (fa[f]!=tar)
	  rotate(get(x)==get(f)?f:x);
	if (!tar) root=x;
}
int find(int x)
{
	int now=root;
	while (true)
	{
		pushdown(now);
		if (x<=size[ch[now][0]])
		 now=ch[now][0];
		else
		 {
		 	int ans=size[ch[now][0]]+1;
		 	if (ans==x) return now;
		 	x-=ans; now=ch[now][1];
		 }
	}
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d\n",&n,&m);
	scanf("%s",s+1);
	for (int i=1;i<=n;i++)
	 if (s[i]=='(')  pos[i]=-1;
	 else pos[i]=1;
	root=build(0,n+1,0);
	for (int i=1;i<=m;i++)
	 {
	 	int opt,x,y;
	 	scanf("%d%d%d",&opt,&x,&y);
	 	int l=find(x); int r=find(y+2);
	 	//cout<<l<<" "<<r<<endl;
	 	splay(l,0); splay(r,root);
	 	int t=ch[ch[root][1]][0];
	 	if (opt==0)
	 	 {
	 	 	int ans=prefx[t]; int ans1=sufn[t]; 
			if (ans1>0) ans1=0;
			else ans1=-ans1;
			if (ans<0) ans=0;
	 	 	if (ans%2||ans1%2)  printf("%d\n",(ans+ans1)/2+1);
	 	 	else printf("%d\n",(ans+ans1)/2);
		 }
		if (opt==1)
		 {
		 	neg[t]^=1;
		 	change1(t); update(ch[root][1]); update(root);
		 }
		if (opt==2)
		 {
		    rev[t]^=1;
			change(t);	update(ch[root][1]); update(root);
		 }
	 }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值