「CTSC2017」网络


题目

点这里看题目。

分析

可以说,这是一道思路比较常规的题目,但是某些看待问题的角度还是可以学习的。

首先从题目中注意到两个关键信息:

  • 最长最短路——直径——联系到原树的直径;
  • 显然答案是可以二分的;

原树的直径这个东西怎么用?我们不妨先把原树的直径提作“根”。下面是一个显然的结论:

新加边的一个端点一定落在了直径上。

这个东西太弱了,我们进行加强就可以想到:

新加边的两个端点一定都落在直径上

第一个很好证明,我们考虑第二个:

先将直径 \((u,v)\) 作为根,我们假设新加边的一个端点 \(x\) 在直径上,另一个 \(y\) 不在(\(y\) 更靠近 \(v\));不在直径上的端点对应直径上的根为 \(w\)

网络.png

尝试说明 \(y\) 向根走一步之后答案更优:先将环提作根,而后只考虑 \(w\) 的子树;当 \(w\) 子树内任意一个点作为根时,离它最远的点之一一定是 \(v\)。所以当 \(y\)\(w\) 走一步之后,\(v\)\(y\) 的距离一定会变小,才有可能使答案更优。而其它结点到 \(y\) 的贡献实际上不需要考虑。

所以,设直径为 \(l\)。我们将直径 \((u,v)\) 上的点拉成一行,从 \(u\)\(v\) 按照 \(1,2,\dots,l+1\) 编号。设 \(s_{i}\) 为第 \(i\) 个点到 \(u\) 的距离,\(f_i\) 为第 \(i\) 个点的以直径为根的子树内的最远点距离;假设新加边为 \((a,b),a。这样,我们可以数量化新加边之后,经过原直径上至少一条边的新直径:

\[\max_{1\le i

当我们二分好答案为 \(D\) 之后,我们就是要求,对于所有 \(i,都有:

\[\min\{f_i+f_j+s_j-s_i,f_i+f_j+|s_a-s_i|+|s_b-s_j|+L\}\le D \]

\(\min\) 的组合含义是存在任一。注意到前面一项和如何选择 \(a,b\) 没有关系,因此不满足前一项的一定要满足后一项。这个绝对值比较尴尬,但我们知道绝对值可以变成 \(\max\),因此最终我们还是得到了四组 \(\le D\) 且需要同时成立的限制。

整理一下,我们要求 \(i\(f_i+f_j+s_j-s_i>D\),这可以被转化为二维偏序问题,使用树状数组维护若干个最值,最终得到如下限制:

\[\begin{cases} l_+\le s_a+s_b\le r_+\\ l_-\le s_a-s_b\le r_- \end{cases} \]

之后就容易 \(O(n)\) 处理了;这样总的复杂度为 \(O(n\log n\log l)\)

小结:

  1. 一些必要的联想:与最长最短路相关的,一般都可以想到直径和二分
  2. 这个证明思路可以学习一下,不过实际上很好猜;
  3. 数量化是一个很重要的步骤。形式化地描述某些东西总不是坏事,甚至我们可以看清它的结构
  4. 关注 \(\min,\max\) 在不等式中的组合含义,以及处理 \(|a|\) 的方法。

代码

#include 
#include 
#include 

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const LL INF = 4e18;
const int MAXN = 1e5 + 5;

template
void read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template
void write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' );
}

template
_T MIN( const _T a, const _T b ) {
    return a < b ? a : b;
}

template
_T MAX( const _T a, const _T b ) {
    return a > b ? a : b;
}

struct Edge {
    int to, nxt; LL w;
} Graph[MAXN << 1];

inline void Down( int &x ) { x &= x - 1; }
inline void Up( int &x ) { x += x & ( - x ); }

struct MinBIT {
    LL val[MAXN]; int n;

    MinBIT(): val{}, n( 0 ) {}

    void Init( const int N ) { n = N; rep( i, 1, n ) val[i] = INF; }
    void Update( int x, const LL v ) { for( ; x <= n ; Up( x ) ) val[x] = MIN( val[x], v ); }
    LL   Query( int x ) { LL ret = INF; for( ; x ; Down( x ) ) ret = MIN( ret, val[x] ); return ret; }
};

struct MaxBIT {
    LL val[MAXN]; int n;

    MaxBIT(): val{}, n( 0 ) {}

    void Init( const int N ) { n = N; rep( i, 1, n ) val[i] = - INF; }
    void Update( int x, const LL v ) { for( ; x <= n ; Up( x ) ) val[x] = MAX( val[x], v ); }
    LL   Query( int x ) { LL ret = - INF; for( ; x ; Down( x ) ) ret = MAX( ret, val[x] ); return ret; }
};

MinBIT mn;
MaxBIT mx;

std :: set s;

LL sufMx[MAXN], sufMn[MAXN];
int seq1[MAXN], seq2[MAXN];

LL mst[MAXN], far[MAXN], pref[MAXN];
LL dist[MAXN];

int pat[MAXN], tot;
bool onPat[MAXN];

int head[MAXN], cnt = 1;

LL len, adUp, adDn, sbUp, sbDn;
int N;

void AddEdge( const int from, const int to, const LL W ) {
    Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
    Graph[cnt].w = W, head[from] = cnt;
}

int BFS( const int S ) {
    static int q[MAXN] = {};
    int h = 1, t = 0;
    rep( i, 1, N ) dist[i] = INF;
    dist[q[++ t] = S] = 0;
    while( h <= t ) {
        int u = q[h ++];
        for( int i = head[u], v ; i ; i = Graph[i].nxt )
            if( dist[v = Graph[i].to] > dist[u] + Graph[i].w )
                dist[q[++ t] = v] = dist[u] + Graph[i].w;
    }
    int mx = 1;
    rep( i, 2, N )
        if( dist[mx] < dist[i] )
            mx = i;
    return mx;
}

bool GetPath( const int u, const int fa, const int tar ) {
    bool ret = false;
    if( u == tar ) ret = true;
    else
        for( int i = head[u], v ; i ; i = Graph[i].nxt )
            if( ( v = Graph[i].to ) ^ fa )
                ret |= GetPath( v, u, tar );
    if( ret ) pat[++ tot] = u;
    return ret;
}

LL Init( const int u, const int fa ) {
    LL ret = 0, mx = 0, sm = - INF, tmp;
    for( int i = head[u], v ; i ; i = Graph[i].nxt )
        if( ( v = Graph[i].to ) ^ fa && ! onPat[v] ) {
            ret = MAX( ret, Init( v, u ) ); 
            tmp = mst[v] + Graph[i].w;
            if( mx <= tmp ) sm = mx, mx = tmp;
            else if( sm < tmp ) sm = tmp;
        }
    mst[u] = mx;
    return MAX( ret, sm + mx );
}

bool Chk( const LL lim ) {
    adUp = sbUp = INF, adDn = sbDn = - INF;
    rep( i, 1, tot ) seq1[i] = seq2[i] = i;
    std :: sort( seq1 + 1, seq1 + 1 + tot, 
        [] ( const int &a, const int &b ) -> bool {
            return far[a] - pref[a] < far[b] - pref[b];
        } );
    std :: sort( seq2 + 1, seq2 + 1 + tot,
        [] ( const int &a, const int &b ) -> bool {
            return far[a] + pref[a] < far[b] + pref[b];
        } );
    mn.Init( tot ), mx.Init( tot );
    for( int i = tot, j = 1 ; j <= tot ; j ++ ) {
        int u = seq2[j];
        while( i > 1 && ( far[seq1[i]] - pref[seq1[i]] ) + ( far[u] + pref[u] ) > lim ) 
            mn.Update( seq1[i], pref[seq1[i]] - far[seq1[i]] ),
            mx.Update( seq1[i], pref[seq1[i]] + far[seq1[i]] ), i --;
        adUp = MIN( adUp, lim - len + pref[u] - far[u] + mn.Query( u - 1 ) );
        sbUp = MIN( sbUp, lim - len - pref[u] - far[u] + mn.Query( u - 1 ) );
        sbDn = MAX( sbDn, - lim + len - pref[u] + far[u] + mx.Query( u - 1 ) );
        adDn = MAX( adDn, - lim + len + pref[u] + far[u] + mx.Query( u - 1 ) );
    }
    int l1 = tot + 1, r1 = tot, l2 = 1, r2 = 0;
    rep( i, 1, tot ) {
        while( l1 > 1 && pref[l1 - 1] >= adDn - pref[i] ) l1 --;
        while( r1 >= 1 && pref[r1] > adUp - pref[i] ) r1 --;
        while( l2 <= tot && pref[l2] < sbDn + pref[i] ) l2 ++;
        while( r2 < tot && pref[r2 + 1] <= sbUp + pref[i] ) r2 ++;
        if( MAX( l1, l2 ) <= MIN( r1, r2 ) ) return true;
    }
    return false;
}

int main() {
    while( true ) {
        cnt = 1;
        read( N ), read( len );
        if( ! N && ! len ) break;
        rep( i, 1, N ) head[i] = 0, onPat[i] = false;
        rep( i, 1, N - 1 ) {
            int u, v; LL w;
            read( u ), read( v ), read( w );
            AddEdge( u, v, w ), AddEdge( v, u, w ); 
        }
        int beg = BFS( 1 ), ed = BFS( beg );
        tot = 0, GetPath( beg, 0, ed );
        std :: reverse( pat + 1, pat + 1 + tot );
        pref[1] = 0;
        LL l = 0, r = dist[ed], mid;
        rep( i, 1, tot ) onPat[pat[i]] = true;
        rep( i, 1, tot ) {
            l = MAX( l, Init( pat[i], 0 ) );
            far[i] = mst[pat[i]];
            if( i == tot ) continue;
            for( int j = head[pat[i]] ; j ; j = Graph[j].nxt )
                if( Graph[j].to == pat[i + 1] )
                    pref[i + 1] = pref[i] + Graph[j].w;
        }
        while( l < r ) {
            mid = ( l + r ) >> 1;
            if( Chk( mid ) ) r = mid;
            else l = mid + 1;
        }
        write( l ), putchar( '\n' );
    }
    return 0;
}