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

寂静的海底

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

【题解】 P8595 一个网的路 树形dp

一眼丁真鉴定为 难度贴近 CSPT3(?),简单题。

O(n) solution

题意:

给你一片森林,你需要进行最少的操作次数把他变成链。

  • 删除一个点周围的所有存在的边

  • 连接一条边


先分析性质:

最后的链之间是联通的,所以最后所有连通块会被联通到一起,需要 n1m 条边,这些边应该在最后被链接,因为在之前连接两个不同连通块不会产生更优秀的决策。

所以我们的策略应该是先把每个联通块做成一条链,最后在合起来。

连通块之间独立,考虑如何计算连通块。

有两类操作不好统计,试图把第二类操作给绑定在第一类上。

连接显然只会连接两条链的两个端点,否则肯定不优。

我们一定是把每个连通块炸成若干条链,最后再连起来,连接次数为炸掉的边数。

每一条边被炸掉都会分离出两个连通块,在后来需要用一条边连接以将连通块数减少 1 , 所以我们可以把代价归到第一类操作上。

  • 删除一个点周围所有存在的边,其代价为删除的边数 + 1

1 表示本次操作代价,其它删除的边都会增加一次二类操作的代价。


然后就可以非常简单地 dp 了 , 发现子树特征信息只和这个点的状态相关,所以记录下这个点的状态即可。

fi,0 表示炸掉这个点的,子树内形成若干链的最小代价。

fi,1 表示不炸掉这个点,且保留父亲和他连边(即儿子内只能连最多一条边)的代价。

fi,2 表示不炸掉这个点,且不保留父亲和他连边(即儿子内只能最多两条边)的代价。

初值:

fleaf,0=2,fleaf,1=fleaf,2=0 , leaf 为叶子。

转移:

fu,0=s is a son of umin{fson,01,fson,2}

儿子可以选择炸或者不保留父亲之间的边,如果炸的话要减去父亲 -> 儿子这条连边,因为儿子炸掉后父亲不用炸了。

fu,1=mins is a son of u,ts1fs,ts(ts{0,1})

fu,2=mins is a son of u,ts2fs,ts(ts{0,1})

感觉这个转移用文字阐述比式子方便的多。

如果和父亲之间保留了连边,最多就只能和一个儿子保留连边,其它儿子都要炸掉。

如果和父亲之间保留了连边,最多就只能和两个儿子保留连边,其它儿子都要炸掉。

具体转移可以选择求出 fs,1fs,0 的最大值和次大值,也可以选择做一个大小为 1 和 2 的背包 , 题解直接懒,使用了 sort , 故代码复杂度是 O(nlogn) , 使用 nth_element 即可做到 O(n)

代码(只放了dfs)

void dfs(int x,int fa) {
    vis[x] = 1; 
    if(ed[x].size() == 0 || (ed[x][0] == fa && ed[x].size( ) == 1) )  {
        f[x][0] = 2; f[x][1] = 0 ; f[x][2] = 0 ;
        return ; // 这里写的很臭,其实不用初值的。
    }
    Array sv ; 
    f[x][0] = (int)ed[x].size( ) + 1 , f[x][1] = 0 , f[x][2] = 0;
    for(int p:ed[x]) {
        if(p == fa) continue;
        dfs(p , x) ;
        f[x][0] += min(f[p][0] - 1 , f[p][2]);
        f[x][1] += f[p][0] ;
        f[x][2] += f[p][0] ; 
        sv.emplace_back(f[p][1] - f[p][0]); 
    }    
    sort(sv.begin( ) , sv.end( )) ;
    /*you can change it to nth_element , then it's O(n)*/
    f[x][1] += min(sv[0] , 0) ;
    f[x][2] += min(sv[0] , 0) ;
    (sv.size() > 1) && (f[x][2] += min(sv[1] , 0));
}

完整版 | 三月月可爱

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