bzoj2209 [Jsoi2011]括号序列(splay)

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。


分析:
之前做过一道匹配括号的题,标算是dp
但是那道题有两种括号,
这道题在括号种类上简化了一下
但是多出了两个操作

一开始我有一个简单的想法:
所有的“(”赋值成-1,“)”赋值成1
只需要判断区间的和是不是0即可
但是这样是连样例都过不了的

匹配询问:

实际上转换成+-1的思想是正确的:
我们将括号序列改成-1与1的序列,用-1表示“(”,用1表示“)”
那么一段区间中前缀和的最大值就是未匹配的“)”的数量(mx1表示),
后缀和的最小值为未匹配的“(”的数量(mn2表示)。

如果未匹配的“(”,“)”都是偶数的话,那答案就是(mx1+mn2)/2
如果未匹配的“(”,“)”都是奇数的话,那答案就是(mx1+mn2)/2+1
反转操作:

把所有权值都*-1(mx1,mx2,mn1,mn2,sum,v)
打反转标记,swap(mx1,mn1),swap(mx2,mn2)
因为反转之后-1与1都会发生变化,而且取反后最大值与最小值都会发生大小交换

翻转操作:

splay的经典操作(文艺平衡树),交换左右子树,打翻转标记,
swap(mx1[bh],mx2[bh]),swap(mn1[bh],mn2[bh]),因为以前的前缀变成了后缀,数值并未发生改变

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

对于问题的解决方法就到这里,下面就是细节的实现


我们先具象的了解一下mx和mn,并且弄明白翻转操作和反转操作的原理:

这里写图片描述

整道题难度都集中在了update函数上:

void update(int bh)
{
    if (!bh) return;
    mx1[0]=mx2[0]=-INF; mn1[0]=mn2[0]=INF; v[0]=0;      //防止儿子不存在(=0)的时候出错 

    int lc=ch[bh][0];
    int rc=ch[bh][1];
    size[bh]=1+size[lc]+size[rc];
    sum[bh]=v[bh]+sum[lc]+sum[rc];

    int t;
    t=max(sum[lc]+v[bh],sum[lc]+v[bh]+mx1[rc]);
    mx1[bh]=max(mx1[lc],t);
    t=min(sum[lc]+v[bh],sum[lc]+v[bh]+mn1[rc]);
    mn1[bh]=min(mn1[lc],t);
    t=max(sum[rc]+v[bh],sum[rc]+v[bh]+mx2[lc]);
    mx2[bh]=max(mx2[rc],t);
    t=min(sum[rc]+v[bh],sum[rc]+v[bh]+mn2[lc]);
    mn2[bh]=min(mn2[rc],t);
}

实际上具体思路就和线段树的差不多
以mx1为例:

mx1[fa]的取值有三种可能:mx1[lc] , sum[lc]+v[fa] , sum[lc]+v[fa]+mx1[rc]

tip

注意update的写法

//这里写代码片
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int INF=0x33333333;
const int N=500010;
int v[N],sum[N],ch[N][2],pre[N],size[N];
int mx1[N],mx2[N],mn1[N],mn2[N];     //mx1:最大前缀和,mx2:最大后缀和,mn1:最小前缀和,mn2:最小后缀和 
bool rev[N],mul[N];
int n,top=0,m,a[N],root;
char s[N];

int get(int bh)
{
    return ch[pre[bh]][0]==bh? 0:1;
}

void update(int bh)
{
    if (!bh) return;
    mx1[0]=mx2[0]=-INF; mn1[0]=mn2[0]=INF; v[0]=0;      //防止儿子不存在(=0)的时候出错 

    int lc=ch[bh][0];
    int rc=ch[bh][1];
    size[bh]=1+size[lc]+size[rc];
    sum[bh]=v[bh]+sum[lc]+sum[rc];

    int t;
    t=max(sum[lc]+v[bh],sum[lc]+v[bh]+mx1[rc]);
    mx1[bh]=max(mx1[lc],t);
    t=min(sum[lc]+v[bh],sum[lc]+v[bh]+mn1[rc]);
    mn1[bh]=min(mn1[lc],t);
    t=max(sum[rc]+v[bh],sum[rc]+v[bh]+mx2[lc]);
    mx2[bh]=max(mx2[rc],t);
    t=min(sum[rc]+v[bh],sum[rc]+v[bh]+mn2[lc]);
    mn2[bh]=min(mn2[rc],t);
}

void change(int bh)                   //处理翻转操作 
{
    swap(mx1[bh],mx2[bh]);
    swap(mn1[bh],mn2[bh]);
}

void change1(int bh)                  //处理反转操作(括号取反) 
{
    sum[bh]*=-1; v[bh]*=-1;
    mx1[bh]*=-1; mx2[bh]*=-1;
    mn1[bh]*=-1; mn2[bh]*=-1;
    swap(mx1[bh],mn1[bh]);
    swap(mx2[bh],mn2[bh]);
} 

void push(int bh)
{
    if (!bh) return;
    if (rev[bh])
    {
        if (ch[bh][0]) change(ch[bh][0]),rev[ch[bh][0]]^=1;
        if (ch[bh][1]) change(ch[bh][1]),rev[ch[bh][1]]^=1;
        swap(ch[bh][0],ch[bh][1]);
        rev[bh]^=1;
    }
    if (mul[bh])
    {
        if (ch[bh][0]) change1(ch[bh][0]),mul[bh[ch][0]]^=1;
        if (ch[bh][1]) change1(ch[bh][1]),mul[bh[ch][1]]^=1;
        mul[bh]^=1;
    }
}

void rotate(int bh)
{
    int fa=pre[bh];
    int grand=pre[fa];
    int wh=get(bh);
    ch[fa][wh]=ch[bh][wh^1];
    pre[ch[fa][wh]]=fa;
    ch[bh][wh^1]=fa;
    pre[fa]=bh;
    pre[bh]=grand;
    if (grand) ch[grand][ch[grand][0]==fa? 0:1]=bh;
    update(fa);
    update(bh);
}

void down(int bh)
{
    if (pre[bh]) down(pre[bh]);
    push(bh);
}

void splay(int bh,int mb)
{
    down(bh);
    for (int fa;(fa=pre[bh])!=mb;rotate(bh))
        if (pre[fa]!=mb)
            rotate(get(bh)==get(fa)? fa:bh);
    if (mb==0) root=bh;
}

int find(int x)
{
    int now=root;
    while (1)
    {
        push(now);
        if (size[ch[now][0]]>=x) now=ch[now][0];
        else{
            int tmp=(ch[now][0]? size[ch[now][0]]:0);
            tmp++;
            if (x<=tmp) return now;
            x-=tmp;
            now=ch[now][1];
        }
    }
}

int build(int l,int r,int fa)
{
    if (l>r) return 0;
    int now=++top;
    int mid=(l+r)>>1;
    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    pre[now]=fa; 
    v[now]=a[mid];    ///
    update(now);
    return now;
}

int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    for (int i=1;i<=n;i++) 
        if (s[i]=='(') a[i]=-1;
        else a[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 xx=find(x);
        int yy=find(y+2);
        splay(xx,0);
        splay(yy,xx);
        int now=ch[ch[root][1]][0];

        if (opt==0){
            int ans1=mx1[now]; int ans2=mn2[now];
            //前缀和的最大值就是未匹配的")"的数量,后缀和的最小值为未匹配的"("的数量
            if (ans2>0) ans2=0;
            else ans2=-ans2;
            if (ans1<0) ans1=0;
            if ((ans1&1)||(ans2&1)) printf("%d\n",(ans1+ans2)/2+1);
            else printf("%d\n",(ans1+ans2)/2);
        }
        else if (opt==1){
            mul[now]^=1;
            change1(now); 
            update(ch[root][1]); update(root);
        }
        else{
            rev[now]^=1;
            change(now); 
            update(ch[root][1]); update(root);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值