[WC2010]重建计划
- problem
- solution
- code
problem
洛谷指路
solution
一看那个道路平均价值的式子:AvgValue=∑e∈Sv(e)∣S∣\text{AvgValue}=\frac{\sum_{e\in S}v(e)}{|S|}AvgValue=∣S∣∑e∈Sv(e) 就是 0/1分数规划
的样子。
所以考虑二分最终的答案 midmidmid,考虑是否有一条路径的平均价值 ≥mid\ge mid≥mid,但这个形式仍然跟具体路径的条数有关。
不妨将每条边的边权都减去 midmidmid,问题转化为 check\text{check}check 是否有一条长度在 [L,U][L,U][L,U] 内且边权和 ≥0\ge 0≥0 的路径。
路径问题就套路的点分治,处理经过分治中心的路径情况。
一条过分治中心的路径是由两个不同子树内的路径拼接起来的。
所以单独依次考虑分治中心的每个子树。
首先得 dfs
一遍子树,求出子树内每个点的距离分治中心的路径权值,即 dis[i]
。
考虑现在枚举的子树和之前已经处理过的子树拼接出来的路径。
要求两个路径的长度之和在 [L,U][L,U][L,U] 的范围内,而且我们并不关心具体是哪两个点,只关心这两个点的深度。并且整个操作都是为了找一条 ≥0\ge 0≥0 的路径。
所以直接枚举长度(深度),并且长度相同的点取路径权值更大的点。
f[i]
:前面处理过的子树中点深度为 iii 的,到分治中心路径权值的最大值。
g[i]
:当前枚举的子树中点深度为 iii 的,到分治中心路径权值的最大值。
那么每次处理下一个子树前,还要进行二者的合并,即 f[i]=max(f[i],g[i])
。
这个信息的维护通过 dfs
每个子树可以获得,即 g[dep[u]]=max(g[dep[u]],dis[u])
。
然后枚举两个深度求:maxL≤i+j≤U{f[i]+g[j]}\max_{L\le i+j\le U}\Big\{f[i]+g[j]\Big\}maxL≤i+j≤U{f[i]+g[j]}。
转换一下当现在枚举的子树选择的深度 jjj 固定时,有 L−j≤i≤U−jL-j\le i\le U-jL−j≤i≤U−j。
发现随着 jjj 的增大,iii 枚举的范围也在增大。
所以可以单调队列维护 iii 优化成线性。
如果只是这样,跑出来可能会发现 TLE
。
考虑这样一种情况:L=1,U=n2L=1,U=\frac n2L=1,U=2n,数据构造了一条点数为 n2\frac n22n 的链,然后在链的一端构造了一个点数为 n2\frac n22n 的菊花。
分治中心显然是会选在菊花和链的交界处。
然后如果第一棵子树选择访问了那一条链,更新了长度在 111 到 n2\frac n22n 的最优答案。
再去访问菊花中的每一个点的时,则每一次都需要枚举 1∼n21\sim \frac n21∼2n,对单调队列进行初始化,结果导致光这一层分治的复杂度就变成了 O(n2)O(\frac n2)O(2n)。
事实上,这个问题源于每一次单调队列初始化的复杂度其实是 O(min(U−1,MaxLen))O\Big(min(U−1,MaxLen)\Big)O(min(U−1,MaxLen)) 的,其中MaxLenMaxLenMaxLen 为之前访问的所有子树中路径的最长长度(最大深度)。
如果我们在本身 UUU 就比较大的情况下,选择先操作较长的子树,将 MaxLenMaxLenMaxLen 变到很大,然后再不断地进行后续单调队列的操作,那复杂度肯定就炸了。
解决方法就是启发式合并,按照子树的最长长度(最大深度)排序,从小到大处理子树。
即 sort(s+1,s+cnt+1,[](int x, int y){ return h[x] < h[y]; })
。
这样就有正确的时间复杂度 O(nlog2n)O(n\log^2n)O(nlog2n)。
code
#include <bits/stdc++.h>
using namespace std;
#define eps 1e-4
#define maxn 100005
#define inf 0x7f7f7f7f
double ans;
int n, L, U, N, Max, root, cnt = 1;
double f[maxn], g[maxn], dis[maxn];
int h[maxn], s[maxn], q[maxn], dep[maxn], len[maxn], siz[maxn], head[maxn];
bool vis[maxn];
struct node { int to, nxt, d; }E[maxn << 1];void dfs( int u, int fa ) {siz[u] = 1; int maxson = 0;for( int i = head[u];i;i = E[i].nxt ) {int v = E[i].to;if( vis[v] or v == fa ) continue;else dfs( v, u );siz[u] += siz[v];maxson = max( maxson, siz[v] );}maxson = max( maxson, N - siz[u] );if( maxson < Max ) Max = maxson, root = u;
}void dfs1( int u, int fa ) {dep[u] = dep[fa] + 1, h[u] = 1;for( int i = head[u];i;i = E[i].nxt ) {int v = E[i].to, w = E[i].d;if( vis[v] or v == fa ) continue;else len[v] = w, dfs1( v, u ), h[u] = max( h[u], h[v] + 1 );}
}void dfs2( int u, int fa, double mid ) {dis[u] = dis[fa] + len[u] - mid, g[dep[u]] = max( g[dep[u]], dis[u] );for( int i = head[u];i;i = E[i].nxt ) {int v = E[i].to;if( vis[v] or v == fa ) continue;else dfs2( v, u, mid );}
}bool check( double mid ) {double ret = -inf;for( int i = 1;i <= cnt;i ++ ) {for( int j = 1;j <= h[s[i]];j ++ ) g[j] = -inf;dfs2( s[i], root, mid );int head = 1, tail = 0;for( int j = 0;j <= h[s[i]];j ++ ) {while( head <= tail and q[head] > U - j ) head ++;if( L - j <= h[s[i]] ) {while( head <= tail and f[q[tail]] < f[L - j] ) tail --;q[++ tail] = L - j;}if( head <= tail ) ret = max( ret, f[q[head]] + g[j] );}for( int j = 1;j <= h[s[i]];j ++ ) f[j] = max( f[j], g[j] );}return ret > 0;
}void solve( int u ) {vis[u] = 1, dis[u] = 0, dep[0] = -1;dfs1( u, 0 );cnt = 0;for( int i = head[u];i;i = E[i].nxt )if( ! vis[E[i].to] ) s[++ cnt] = E[i].to;if( ! cnt ) return;sort( s + 1, s + cnt + 1, []( int x, int y ) { return h[x] < h[y]; });double l = ans, r = 1e6;while( r - l > eps ) {double mid = ( l + r ) / 2;for( int i = 1;i <= h[s[cnt]];i ++ ) f[i] = -inf; f[0] = 0;if( check( mid ) ) ans = max( ans, mid ), l = mid;else r = mid;}for( int i = head[u];i;i = E[i].nxt ) {int v = E[i].to;if( vis[v] ) continue;N = siz[v], Max = inf;dfs( v, u );solve( root );}
}int main() {scanf( "%d %d %d", &n, &L, &U );for( int i = 1, u, v, w;i < n;i ++ ) {scanf( "%d %d %d", &u, &v, &w );E[cnt] = { v, head[u], w }, head[u] = cnt ++;E[cnt] = { u, head[v], w }, head[v] = cnt ++;}Max = inf, N = n;dfs( 1, 0 );solve( root );printf( "%.3f\n", ans );return 0;
}