LOJ#2553. 「CTSC2018」暴力写挂-动态加点边分治

说在前面

上午看别人的代码,把这题弄懂了
下午开始写,然后肝到现在,恶心死了…
真·写一题学一堆


题目

LOJ#2553传送门
看题可戳传送门


解法

典型猫式数据结构,乱搞拿高分,才不去写什么正解呢哼

行吧,这道题呢,我们把一对点在这道题中的「距离」用语言描述出来,就是 T T u,v u , v 到根的链并,减去 T T ′ u,v u , v 的LCA深度
那么一个很显然(并不)的思路就是,在 T T ′ 上枚举LCA, u,v u , v 分居不同子树,这个比较经典的模型,一般都是用可合并的数据结构维护,顺便计算贡献

但是启发式合并是GG的,首先启发式合并自带一个 logn log ⁡ n 。启发式合并后,还剩下的问题是「 u u 和某个点集中的点 v v 形成的链并,权值最大是多少」。可以把贡献拆成 v v 到LCA的权值和 + u u 到根的权值和,然后用树剖统计,这样又有了 log2n log 2 ⁡ n ,不太可过(附注:关于贡献维护,v到LCA可看作dep[v]-dep[LCA],于是每个节点存 -dep[LCA]最大值(这是个常量),已加入的dep[v]最大值,以及dep[v]-dep[LCA]最大值即可。此方法是me临时yy的,如果存在问题的话,请dalao指正)

所以我们需要换一种数据结构来维护。统计链的信息,还可以使用点分治。在这道题中,需要的是和别的子树内的点成链,还需要动态加入,所以复杂度是与点度相关的,还是不ok

因此我们使用边分治!边分治的话,因为一次只会切掉一条边,从而最后一定是一个二叉树,维护很方便
另外,因为边分治一次只能切一条边,如果原图是个菊花,复杂度就不对了。为了解决这个问题,我们可以强行向图里塞一堆点,把原树直接改造成一个二叉树,并且让这些点与周围点连边的边权都是0,这样也不会影响答案
然后关于动态加点,我们需要先把这个边分树建出来,之后加点的时候才知道应该放在什么位置。(线段树的动态加点就不需要提前建一颗树,因为我们可以通过 Lmid L ≤ m i d 或者 R>mid R > m i d 的判断来进行及时定位,从而知道需不需要向左/右走。然而点/边分是不行的,及时定位的代价太高)

关于实现细节,还是看代码叭…反正挺多的me已经说不清楚了


#include 
#include 
#include 
#include 
using namespace std ;

int N , vcnt ;
long long toRoot[750005] ;
struct Path{
    int to , len ;
    Path( int t = 0 , int l = 0 ): to( t ) , len( l ) {} ;
} son[750000] ;
vector p1[750000] , p2[750000] ;

void In( int t1 , int t2 , int t3 , vector *p ){
    p[t1].push_back( Path( t2 , t3 ) ) ;
    p[t2].push_back( Path( t1 , t3 ) ) ;
}
template <typename T>
void smax( T &x , T y ){ if( x < y ) x = y ; }

void dfs_rebuild( int u , int f ){
    int ba = 0 , fr = 1 ;
    for( vector::iterator it = p1[u].begin() ; it != p1[u].end() ; it ++ ){
        int v = it->to ; if( v == f ) continue ;
        toRoot[v] = toRoot[u] + it->len ;
        dfs_rebuild( v , u ) ;
    } for( vector::iterator it = p1[u].begin() ; it != p1[u].end() ; it ++ )
        if( it->to != f ) son[++ba] = *it ;

    while( ba - fr >= 2 ){ // 3 or more
        Path s1 = son[fr++] , s2 = son[fr++] ;
        int w = ++ vcnt ; son[++ba] = Path( w , 0 ) ;
        In( w , s1.to , s1.len , p1 ) ;
        In( w , s2.to , s2.len , p1 ) ;
    } p1[u].clear() ;
    while( ba >= fr ) In( u , son[fr].to , son[fr].len , p1 ) , fr ++ ;
}

int ls[1500000] , rs[1500000] , fa[1500000] ;
int dif[750000] , siz[750000] , SIZ , dep[750000] , val[1500000] ;
long long dist[25][750000] ;
void dfs_getdis( int u , int f , int dep ){
    for( vector::iterator it = p1[u].begin() ; it != p1[u].end() ; it ++ ){
        int v = it->to ; if( v == f ) continue ;
        dist[dep][v] = dist[dep][u] + it->len ;
        dfs_getdis( v , u , dep ) ;
    }
}

void dfs_Gr( int u , int f , int &G1 , int &G2 ){
    siz[u] = 1 ;
    for( vector::iterator it = p1[u].begin() ; it != p1[u].end() ; it ++ ){
        int v = it->to ; if( v == f || v == -1 ) continue ;
        dfs_Gr( v , u , G1 , G2 ) ;
        siz[u] += siz[v] ;
        if( dif[G2] > dif[v] ) G1 = u , G2 = v ;
    } dif[u] = abs( SIZ - 2 * siz[u] ) ;
}

int dfs_getTree( int u , int dep , int ssiz ){
    if( ssiz == 1 ){ ::dep[u] = dep ; return u ; }
    dfs_getdis( u , u , dep ) ;
    int G1(0) , G2(0) , now = ++vcnt ;
    SIZ = ssiz , dfs_Gr( u , u , G1 , G2 ) ;

    for( vector::iterator it = p1[G1].begin() ; it != p1[G1].end() ; it ++ )
        if( it->to == G2 ){ val[now] = it->len , it->to = -1 ; break ; }
    for( vector::iterator it = p1[G2].begin() ; it != p1[G2].end() ; it ++ )
        if( it->to == G1 ){ it->to = -1 ; break ; }
    rs[now] = dfs_getTree( G1 , dep + 1 , ssiz - siz[G2] ) ;
    ls[now] = dfs_getTree( G2 , dep + 1 , siz[G2] ) ;
    fa[ ls[now] ] = fa[ rs[now] ] = now ;
    return now ;
}

void preWork(){
    vcnt = N , dfs_rebuild( 1 , 1 ) ;
    dif[0] = 0x3f3f3f3f , dfs_getTree( 1 , 0 , vcnt ) ;
}

long long lv[23*370000] , rv[23*370000] , ans ;
int id_c , lp[23*370000] , rp[23*370000] , mp[23*370000] , root[370000] ;
int Merge( int x , int y , long long d ){
    if( !x || !y ) return x + y ;
    smax( ans , ( lv[x] + rv[y] + val[ mp[x] ] ) / 2 - d ) ;
    smax( ans , ( rv[x] + lv[y] + val[ mp[x] ] ) / 2 - d ) ;
    smax( lv[x] , lv[y] ) , lp[x] = Merge( lp[x] , lp[y] , d ) ;
    smax( rv[x] , rv[y] ) , rp[x] = Merge( rp[x] , rp[y] , d ) ;
    return x ;
}

int insert( int u ){
    for( int i = dep[u] , x = u , las = 0 ; i > 0 ; i -- ){
        mp[++id_c] = fa[u] ;
        lv[id_c] = rv[id_c] = -1LL << 60 ;
        if( u == ls[ fa[u] ] ) smax( lv[id_c] , dist[i][x] + toRoot[x] ) , lp[id_c] = las ;
        if( u == rs[ fa[u] ] ) smax( rv[id_c] , dist[i][x] + toRoot[x] ) , rp[id_c] = las ;
        las = id_c , u = fa[u] ;
    } return id_c ;
}

void dfs_solve( int u , int f , long long d ){
    root[u] = insert( u ) ; smax( ans , toRoot[u] - d ) ;
    for( vector::iterator it = p2[u].begin() ; it != p2[u].end() ; it ++ ){
        int v = it->to ; if( v == f ) continue ;
        dfs_solve( it->to , u , d + it->len ) ;
        root[u] = Merge( root[u] , root[v] , d ) ;
    }
}

void read_( int &x ){
    x = 0 ; bool f = false ;
    char ch = getchar() ;
    while( ch < '0' || ch > '9' ){ if( ch == '-' ) f = true ; ch = getchar() ; }
    while( ch >='0' && ch <='9' ) x = x * 10 + ch - '0' , ch = getchar() ;
    x = f ? -x : x ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 , u , v , w ; i < N ; i ++ ){
        read_( u ) , read_( v ) , read_( w ) ;
        In( u , v , w , p1 ) ;
    } for( int i = 1 , u , v , w ; i < N ; i ++ ){
        read_( u ) , read_( v ) , read_( w ) ;
        In( u , v , w , p2 ) ;
    } preWork() ; dfs_solve( 1 , 1 , 0 ) ;
    printf( "%lld\n" , ans ) ;
}

你可能感兴趣的:(点分树/边分树)