【题解】 CF1712F | 思维 长链剖分 启发式合并
这题你谷咋没题解啊
题意:
给你一棵树边权全为 1 ,询问若把所有叶子两两连一条边长为 的边后的直径。
令 表示离 最近的叶子离它的距离, 可以通过bfs换根求出。
首先,两个点之间的距离显然为
类似统计直径的办法,在合并子树信息时统计答案,有两种暴力的办法:
记录 子树内每种 中 最小的点。
记录 子树内每种 中 最大的点
考虑把第二种拎出来优化,原因稍后说明。
令 表示 子树内 的最大 。
首先合并是容易优化的,启发式地将子树内最深点浅的信息并到浅的儿子上,即可快速地合并信息,复杂度最后说明。
统计答案:
我们需要判断 能否大于 。
假设
在小的那边枚举一下 的值, 我们期望 的距离大于 ,那么有 这样 ans 才可能变大 1 。
之所以选择第二种,因为 是随着 而不下降的*,这样我们就只用判断 和点 的距离能否大于 即可。 如果可以, , 继续判断。
*:因为每个点子树里的点不可能 全比它大,并且相邻两个点的 最多相差 1 ,且叶子节点的 值为 0 , 所以这之间的 值都存在且深度大于它。
合并部分代码
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]) ;
}
这类带有 最大化 的题目,目前已经确定了部分答案,期望有更优答案,就只需要枚举 的 了,一定程度上可以降低复杂度。
复杂度相关:
由于 总共只会扩展 次,这部分是 的。
合并的时候枚举了最深点浅的那边的 值进行更新答案与合并 ,单次合并为 。 因为每个节点都只会在它所在的长链顶端被统计一次,时间复杂度是 的 。
参考了 cf 原题题解。
我:神仙题,神仙做法,扩展答案真的妙。
同学:板
cf 题解:典
我:?
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970951,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步