【题解】 CF1712F | 思维 长链剖分 启发式合并
这题你谷咋没题解啊
题意:
给你一棵树边权全为 1 ,询问若把所有叶子两两连一条边长为 $d$ 的边后的直径。
$n \leq 10^6 , q\leq 10$
令 $f_x$ 表示离 $x$ 最近的叶子离它的距离, $f$ 可以通过bfs换根求出。
首先,两个点之间的距离显然为 $\min\{d + f_u + f_v , dis(u,v)\}$
类似统计直径的办法,在合并子树信息时统计答案,有两种暴力的办法:
记录 $u$ 子树内每种 $dep$ 中 $f$ 最小的点。
记录 $u$ 子树内每种 $f$ 中 $dep$ 最大的点
考虑把第二种拎出来优化,原因稍后说明。
令 $mx(u,i)$ 表示 $u$ 子树内 $f_v= i$ 的最大 $dep_v$。
首先合并是容易优化的,启发式地将子树内最深点浅的信息并到浅的儿子上,即可快速地合并信息,复杂度最后说明。
统计答案:
我们需要判断 $\min\{d + f_u + f_v , dis(u,v)\}$ 能否大于 $ans$ 。
假设 $siz_u \leq siz_v$
在小的那边枚举一下 $f_u$ 的值, 我们期望 $u,v$ 的距离大于 $ans$ ,那么有 $f_v \geq ans - f_u - d - 1$ 这样 ans 才可能变大 1 。
之所以选择第二种,因为 $mx(u,i)$ 是随着 $i$ 而不下降的*,这样我们就只用判断 $mx(v,ans - f_u - d - 1)$ 和点 $u$ 的距离能否大于 $ans$ 即可。 如果可以, $ans\gets ans+1$ , 继续判断。
*:因为每个点子树里的点不可能 $f$ 全比它大,并且相邻两个点的 $f$ 最多相差 1 ,且叶子节点的 $f$ 值为 0 , 所以这之间的 $f$ 值都存在且深度大于它。
合并部分代码
void Merge(int u,int v) {
if(dp[u].size( ) > dp[v].size( )) dp[u].swap(dp[v]);
for(int i = 0 ; i < dp[u].size( ) ; ++ i) {
/*I want the answer to be more than ans , so dp[u][i] , dp[v][j] , that i + j + x > ans */
/* j >= ans - i - t - 1 */
while(1) {
int lower = max(0 , ans - i - t + 1) ;
if(lower >= dp[v].size( )) break;
if(dp[v][lower] + dp[u][i] - 2 * dep[v] > ans) ans ++ ;
else break;
}
}
for(int i = 0 ; i < dp[u].size( ) ; ++ i) {
upmax(dp[v][i] , dp[u][i]) ;
}
}
void dfs(int x) {
for(int p:son[x]) dfs(p) , Merge(p , x);
while(1) {
int lower = max(0 , ans - f[x] - t + 1);
if(lower >= dp[x].size( )) break;
if(dp[x][lower] - dep[x] > ans) ans ++ ;
else break;
}
if(f[x] == dp[x].size( )) dp[x].emplace_back(dep[x]) ;
}
这类带有 最大化 $\min(x,y)$ 的题目,目前已经确定了部分答案,期望有更优答案,就只需要枚举 $y \geq now$ 的 $x$ 了,一定程度上可以降低复杂度。
复杂度相关:
由于 $ans$ 总共只会扩展 $n$ 次,这部分是 $O(n)$ 的。
合并的时候枚举了最深点浅的那边的 $f$ 值进行更新答案与合并 $f_x\leq dep_x$,单次合并为 $O(\min\{maxdep_u,maxdep_v\})$ 。 因为每个节点都只会在它所在的长链顶端被统计一次,时间复杂度是 $O(n)$ 的 。
参考了 cf 原题题解。
我:神仙题,神仙做法,扩展答案真的妙。
同学:板
cf 题解:典
我:?
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970951,谢谢你的阅读或转载!