[BZOJ1095]Hide 捉迷藏--括号序列&&线段树

岛姐 orz

看题解看了接近两个小时才想通这是在干什么
经常看着某一部分就忘了上面一部分要干什么了,然后一脸蒙…
Emmmmm好在最后还是理解到了,十分巧妙

有点晚了,题解有时间再写吧,先贴几个挺不错的题解,一起看可能比较容易理解
—>岛姐的题解
–>博客园-沐阳

UPD at 2018.1.10 me的动态点分治做法传送门

自带大常数的代码

/**************************************************************
    Problem: 1095
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:2212 ms
    Memory:28280 kb
****************************************************************/

/*-----------------------------------------
岛姐orz
这种解法比较神奇
把树上的信息转换成了序列信息
然后对序列进行花式维护

整个题的重点在于线段树的updata函数
通过多维护几个东西,来快速的计算出答案 
-----------------------------------------*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , Q , head[100005] , tp ;
int pos[100005] , cate[300005] , tot , dark[100005] ;
struct Path{
    int pre , to ;
}p[200005] ;
struct Node{
    int dis , RP , RM , LP , LM , a , b ;
    //RP = right_plus , RM = right_minus
    //LP = left_plus , LM = left_minus 
    Node *ls , *rs ;
    void set( int x ){
        /*a代表的是']' 开口向右的那种括号
          b代表的是'[' 开口向左的那种括号
          因为把可以匹配的括号消除之后,剩下的一定是这样的:
          .....]]]]]][[[[[.....
          (a,b)二元组即可表示这个括号 
        */

        /*如果为白点或者括号,就全部赋值为负无限大
          这样保证了,凡是有括号是跨过了白点的,通通都取不到 
        */
        a = ( x == -2 ) ;
        b = ( x == -1 ) ;
        dis = -0x3f3f3f3f ;
        if( x >= 1 && dark[x] ){
            //如果没有关灯,就设为0,相对于负无穷,这就代表开始了 
            RP = RM = LP = LM = 0 ;
        } else RP = RM = LP = LM = -0x3f3f3f3f ;
    }

    void updata(){
        if( rs->a > ls->b ){
            a = ls->a + rs->a - ls->b ; b = rs->b ;
        } else {
            a = ls->a ; b = ls->b - rs->a + rs->b ;
        }
        //因为是左右拼接,所以是ls->Rx + rs->Lx才能拼接起中间部分
        dis = max( ls->dis , rs->dis ) ; 
        dis = max( dis , ls->RM + rs->LP ) ;
        dis = max( dis , ls->RP + rs->LM ) ;
        //因为RP必须与右端联通,所以需要直接 - rs->a + rs->b ,下面同理 
        RP = max( ls->RP - rs->a + rs->b , ls->RM + rs->a + rs->b ) ;
        RP = max( RP , rs->RP ) ; 
        RM = max( ls->RM + rs->a - rs->b , rs->RM ) ;
        //LM维护的是b-a,因此和RM不太一样,是反过来的 
        LP = max( rs->LP + ls->a - ls->b , rs->LM + ls->a + ls->b ) ;
        LP = max( LP , ls->LP ) ;
        LM = max( rs->LM + ls->b - ls->a , ls->LM ) ;
    }

}w[600005] , *root , *tw = w ;

void In( int t1 , int t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
}

void dfs( int u , int f ){
    /*进入的时候为'['
      在这里把点也直接放进序列,更方便处理
      (就可以很方便的把白点设置成-inf从而阻断跨过白点的序列) 
    */
    cate[++tot] = -1 ;
    pos[ cate[++tot] = u ] = tot ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v != f ) dfs( v , u ) ;
    }
    //出去的时候为']' 
    cate[++tot] = -2 ;
}

Node *build( int lf , int rg ){
    //线段树的日常--build 
    Node *nd = ++tw ;
    if( lf == rg )
        nd->set( cate[lf] ) ;
    else {
        int mid = ( lf + rg ) >> 1 ;
        nd->ls = build( lf , mid ) ;
        nd->rs = build( mid+1 , rg ) ;
        nd->updata() ;
    } return nd ;
}

void Modify( Node *nd , int lf , int rg , int pos ){
    //线段树的日常--modify 
    if( lf == rg ){
        nd->set( cate[lf] ) ;
        return ;
    }
    int mid = ( lf + rg ) >> 1 ;
    if( pos <= mid ) Modify( nd->ls , lf , mid , pos ) ;
    else             Modify( nd->rs , mid+1 , rg , pos ) ;
    nd->updata() ;
}

void solve(){
    char ss[5] ;
    scanf( "%d" , &Q ) ;
    for( int i = 1 , x ; i <= Q ; i ++ ){
        scanf( "%s" , ss ) ;
        switch( ss[0] ){
            case 'G':{
                //答案会在不断地updata后被统计到根节点
                //因此每次询问直接输出根节点的dis即可 
                printf( "%d\n" , root->dis ) ;
                break;
            }
            case 'C':{
                scanf( "%d" , &x ) ;
                //注意这里需要把开关灯状态更新 
                dark[x] ^= 1 ;
                Modify( root , 1 , tot , pos[x] ) ;
                break;
            }
        }
    }
}

//读入优化 等于scanf 
int read_(){
    int rt = 0;
    char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) rt = (rt<<3) + (rt<<1) + ch - '0' , ch = getchar() ;
    return rt;
}

int main(){
    scanf( "%d" , &N ) ;
    //一开始灯泡全部处于关闭状态,注意需要将标记打好 
    for( register int i = 1 ; i <= N ; i ++ ) dark[i] = 1 ;
    //日常建边&&一系列预备操作 
    for( register int i = 1 , t1 , t2 ; i < N ; i ++ ){
        t1 = read_() ; t2 = read_();
        In( t1 , t2 ) ; In( t2 , t1 ) ;
    }
    dfs( 1 , 1 ) ;
    root = build( 1 , tot ) ;
    solve() ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值