• 题解 P4332 【[SHOI2014]三叉神经树】


    呐,一道神仙题 (却被某大佬评价称评分过高)

    首先要考虑什么时候才会导致颜色变化。

    (10pts) 暴力,每次修改暴算答案?

    然而我们发现一个点的修改能且只能影响到它的父亲 (/) 祖先。

    如果我们对每个点维护它的三个儿子中 (1) 的个数。

    可以发现,若一个叶子节点由 (0) 变成了 (1),那么其会对答案造成什么样的影响呢?

    如果其父亲已经有两个及以上(1)或有零个 (1) ,我们可以发现其叶子节点尽管变成了(1)但并没有导致其父亲的颜色造成变化。

    当且仅当其父亲有 (1)(1) 的时候其才会导致颜色变化,类似的讨论,我们可以发现,当一个叶子节点由(0->1)后,有且只有其往上走连续的一段都是 (1) 的数量(1)的点的颜色会发现变化,所以我们可以让其儿子(1)数量(下文称儿子(1)数量为点权)均(+1),然后对于最后顶部((top))的父亲,若(top)是根((1))则改变答案(1号点的颜色),否则将(top)的父亲的点权(+1)

    (1->0)讨论,我们发现也只有从叶子节点开始走连续的一段点权为(2)的点会发生改变,(-1)即可

    那么接下来就是实现这个过程了。

    (1.O(nlog^3n))

    我们可以用树剖,用线段树维护区间最小值和最大值,当前仅当某个区间的最大值和最小值均为(1/2)时才表示这一连续链的点权都是(1/2)

    至于找最上面的位置则可以通过二分/倍增来找,复杂度(O(nlog^3n))

    (2.O(nlog^2n))

    刚刚的那个东西用(LCT)实现的话复杂度就是(O(nlog^2n)),然而常数问题可能速度相差不大,但听说可以过这道题了。

    当然实际上因为这棵树是静态的,所以我们甚至不需要(makeroot,link,split)这些函数。

    在巧妙地利用一下这些性质之后我们可以轻易地得到一个较为简洁且常数相对小一些地代码,具体实现的细节:

    首先是(link),因为数据保证合法(总不至于不是一棵树吧)所以我们可以直接连边,只不过连的是(LCT)中的虚边((t[x].fa = i))

    然后因为这道题的树是静态的,我们如果想得到一条链上的最大最小值怎么做?注意到每次询问的两个点(u,v)一定满足(u)(v)的祖先,所以我们可以考虑先直接(access(v)),然后(Splay(u)),这个时候(u)的右子树就是深度比(u)大的所有点了。

    (3.O(nlogn))

    我们其实可以不需要使用倍增,可以考虑用(LCT)维护深度最大的不为 (1/2) 的点,然后直接将这个点到询问点这一条链(split)出来(注意是在((2))中讲的(split)),然后给他们(+1/-1),然后给父亲(+1/-1)即可。

    这样做就是(O(nlogn))辣。

    当然在代码实现上存在一点点细节问题(QAQ)

    比如我做修改后会影响到深度最大的不为 (1/2) 的点。

    比如现在考虑(+1)操作((0->1)),我们可以发现,(+1)操作执行过程中,最小的不为(1)的点(Splay)了之后其右子树的点权全部都是(1)

    我们给它打了一个(+1)标记之后,其子树内的所有点的(1)全部都变成了(2),这个时候(1)不存在而(2)存在,可以发现我们其实只要(swap(t[x].1,t[x].2))

    类似的对(-1)讨论,我们也只需要 (swap(t[x].1,t[x].2))

    详见代码:

    #include<bits/stdc++.h>
    using namespace std;
    int read() {
        char cc = getchar(); int cn = 0, flus = 1;
        while(cc < '0' || cc > '9') cc = getchar();
        while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
        return cn * flus;
    }
    const int N = 500000 + 5 ;
    #define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
    #define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
    #define ls(x) t[x].son[0]
    #define rs(x) t[x].son[1]
    struct LCT {
        int son[2], fa, add, w[2], col, sum ;
    } t[N * 3];
    
    //w[0]表示深度最大的不为1的点,w[1]表示深度最大的不为2的点。
    //sum表示点权-这个点的儿子中1的数量,如果sum>=2则这个点的颜色为1
    //add是加法标记 
    struct E {
        int to, next, w ;  
    } e[N * 3]; 
    int n, m, head[N * 3], cnt, Ans ;  
    void add( int x, int y ) {
        e[++ cnt] = (E){ y, head[x] }, head[x] = cnt ; 
    }
    //LCT
    bool isroot( int x ) { return ( rs(t[x].fa) != x ) && ( ls(t[x].fa) != x ) ; }
    void modify( int x, int k ) {
        t[x].sum += k, swap( t[x].w[0], t[x].w[1] ), t[x].add += k ; //首先x本身要+k
    	//发现我们只会在一棵本身全都是1/2的一颗树上打标记,最后其实只需要交换左右子树。
    	//然后给这个点的标记 + k 
    }
    void pushmark( int x ) {
        if( t[x].add )
            modify( ls(x), t[x].add ), modify( rs(x), t[x].add ), t[x].add = 0 ;
    }
    void pushup( int x ) {
        t[x].w[0] = t[rs(x)].w[0], t[x].w[1] = t[rs(x)].w[1] ; 
        if( t[x].sum != 1 && !t[x].w[0] ) t[x].w[0] = x ; 
        if( !t[x].w[0] ) t[x].w[0] = t[ls(x)].w[0] ; 
        if( t[x].sum != 2 && !t[x].w[1] ) t[x].w[1] = x ; 
        if( !t[x].w[1] ) t[x].w[1] = t[ls(x)].w[1] ; 
    }
    void rotate( int x ) {
        int f = t[x].fa, ff = t[f].fa, z = rs(f) == x, ch = t[x].son[z ^ 1] ; 
        t[x].fa = ff ; 
        if( !isroot(f) ) t[ff].son[rs(ff) == f] = x ; 
        t[x].son[z ^ 1] = f, t[f].fa = x, t[f].son[z] = ch, t[ch].fa = f ; 
        pushup(f), pushup(x) ; 
    }
    int st[N], top ;
    void Splay( int x ) {
        int now = x; st[++ top] = now ; 
        while( !isroot(now) ) st[++ top] = now = t[now].fa ;
        while( top ) pushmark( st[top --] ) ;
        while( !isroot(x) ) {
            int f = t[x].fa, ff = t[f].fa ; 
            if( !isroot(f) ) ( ( rs(f) == x ) ^ ( rs(ff) == f ) ) ? rotate(x) : rotate(f) ;
            rotate(x) ; 
        }
    } 
    void access( int x ) {
        for( int y = 0; x; x = t[y = x].fa ) Splay(x), rs(x) = y, pushup(x) ;  
    }
    //end
    inline void dfs( int x ) {
        Next( i, x ) dfs(e[i].to), t[x].sum += ( t[e[i].to].col == 1 ) ;
        if( t[x].sum >= 2 ) t[x].col = 1 ; 
    }
    void solve( int x, int c ) {
        int u = t[x].fa ; access(u), Splay(u) ;
        int k = t[u].w[c], ad = ( c == 0 ) ? 1 : -1 ; 
        if( k ) Splay(k), modify( rs(k), ad), pushup(rs(k)), t[k].sum += ad, pushup(k) ;
        else modify( u, ad ), Ans ^= 1, pushup(u);  //如果k根本不存在,那么需要修改当前1号点的颜色。 
        printf("%d
    ", Ans ) ;
    }
    signed main()
    {
        n = read(); int x1, x2, x3, fr = n + 1, ed = 3 * n + 1;
        
        rep( i, 1, n ) x1 = read(), x2 = read(), x3 = read(), t[x1].fa = i, t[x2].fa = i, 
    		t[x3].fa = i, add( i, x1 ), add( i, x2 ), add( i, x3 ) ;
        
        rep( i, fr, ed ) t[i].col = read() ; 
        dfs(1), Ans = t[1].col, m = read() ;
        while( m -- ) x1 = read(), solve( x1, t[x1].col ), t[x1].col ^= 1;
        return 0;
    }
    
  • 相关阅读:
    用find和xargs处理文件名中带空格的文件
    python调用Shell
    python 的sys.argv 和 sys.path.append() 用法和PYTHONPATH环境变量
    pytorch中 model.cuda的作用
    Python中变量和对象的关系
    python中的import、from import以及import as的区别
    常用功能bash脚本
    图像数据增强-图像补边
    python中的glob库函数
    喵哈哈村的七十六
  • 原文地址:https://www.cnblogs.com/Soulist/p/10732393.html
Copyright © 2020-2023  润新知