「CTSC2017」网络
题目
点这里看题目。
分析
可以说,这是一道思路比较常规的题目,但是某些看待问题的角度还是可以学习的。
首先从题目中注意到两个关键信息:
- 最长最短路——直径——联系到原树的直径;
- 显然答案是可以二分的;
原树的直径这个东西怎么用?我们不妨先把原树的直径提作“根”。下面是一个显然的结论:
新加边的一个端点一定落在了直径上。
这个东西太弱了,我们进行加强就可以想到:
新加边的两个端点一定都落在直径上。
第一个很好证明,我们考虑第二个:
先将直径 \((u,v)\) 作为根,我们假设新加边的一个端点 \(x\) 在直径上,另一个 \(y\) 不在(\(y\) 更靠近 \(v\));不在直径上的端点对应直径上的根为 \(w\)。
尝试说明 \(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<b\)。这样,我们可以数量化新加边之后,经过原直径上至少一条边的新直径:
当我们二分好答案为 \(D\) 之后,我们就是要求,对于所有 \(i<j\),都有:
\(\min\) 的组合含义是存在任一。注意到前面一项和如何选择 \(a,b\) 没有关系,因此不满足前一项的一定要满足后一项。这个绝对值比较尴尬,但我们知道绝对值可以变成 \(\max\),因此最终我们还是得到了四组 \(\le D\) 且需要同时成立的限制。
整理一下,我们要求 \(i<j\) 且 \(f_i+f_j+s_j-s_i>D\),这可以被转化为二维偏序问题,使用树状数组维护若干个最值,最终得到如下限制:
之后就容易 \(O(n)\) 处理了;这样总的复杂度为 \(O(n\log n\log l)\)。
小结:
- 一些必要的联想:与最长最短路相关的,一般都可以想到直径和二分;
- 这个证明思路可以学习一下,不过实际上很好猜;
- 数量化是一个很重要的步骤。形式化地描述某些东西总不是坏事,甚至我们可以看清它的结构;
- 关注 \(\min,\max\) 在不等式中的组合含义,以及处理 \(|a|\) 的方法。
代码
#include <set>
#include <cstdio>
#include <algorithm>
#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<typename _T>
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<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b ) {
return a < b ? a : b;
}
template<typename _T>
_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<LL> 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;
}