1. 程式人生 > >題解 P4332 【[SHOI2014]三叉神經樹】

題解 P4332 【[SHOI2014]三叉神經樹】

span 接下來 可能 turn 維護 make 最大值和最小值 while 導致

吶,一道神仙題 (卻被某大佬評價稱評分過高)

首先要考慮什麽時候才會導致顏色變化。

\(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\n", 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;
}

題解 P4332 【[SHOI2014]三叉神經樹】