像潮落潮涌,送我奔向自由。|

寂静的海底

园龄:3年2个月粉丝:59关注:15

【题解】 CF1712F | 思维 长链剖分 启发式合并

这题你谷咋没题解啊


题意:

给你一棵树边权全为 1 ,询问若把所有叶子两两连一条边长为 d 的边后的直径。

n106,q10


fx 表示离 x 最近的叶子离它的距离, f 可以通过bfs换根求出。

首先,两个点之间的距离显然为 min{d+fu+fv,dis(u,v)}

类似统计直径的办法,在合并子树信息时统计答案,有两种暴力的办法:

记录 u 子树内每种 depf 最小的点。

记录 u 子树内每种 fdep 最大的点

考虑把第二种拎出来优化,原因稍后说明。

mx(u,i) 表示 u 子树内 fv=i 的最大 depv

首先合并是容易优化的,启发式地将子树内最深点浅的信息并到浅的儿子上,即可快速地合并信息,复杂度最后说明。

统计答案:

我们需要判断 min{d+fu+fv,dis(u,v)} 能否大于 ans

假设 sizusizv

在小的那边枚举一下 fu 的值, 我们期望 u,v 的距离大于 ans ,那么有 fvansfud1 这样 ans 才可能变大 1 。

之所以选择第二种,因为 mx(u,i) 是随着 i 而不下降的*,这样我们就只用判断 mx(v,ansfud1) 和点 u 的距离能否大于 ans 即可。 如果可以, ansans+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) 的题目,目前已经确定了部分答案,期望有更优答案,就只需要枚举 ynowx 了,一定程度上可以降低复杂度。

复杂度相关:

由于 ans 总共只会扩展 n 次,这部分是 O(n) 的。

合并的时候枚举了最深点浅的那边的 f 值进行更新答案与合并 fxdepx,单次合并为 O(min{maxdepu,maxdepv}) 。 因为每个节点都只会在它所在的长链顶端被统计一次,时间复杂度是 O(n) 的 。


参考了 cf 原题题解。

我:神仙题,神仙做法,扩展答案真的妙。

同学:板

cf 题解:典

我:?

posted @   寂静的海底  阅读(3)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起