【题解】 P8595 一个网的路 树形dp
一眼丁真鉴定为 难度贴近 CSPT3(?),简单题。
题意:
给你一片森林,你需要进行最少的操作次数把他变成链。
-
删除一个点周围的所有存在的边
-
连接一条边
先分析性质:
最后的链之间是联通的,所以最后所有连通块会被联通到一起,需要 条边,这些边应该在最后被链接,因为在之前连接两个不同连通块不会产生更优秀的决策。
所以我们的策略应该是先把每个联通块做成一条链,最后在合起来。
连通块之间独立,考虑如何计算连通块。
有两类操作不好统计,试图把第二类操作给绑定在第一类上。
连接显然只会连接两条链的两个端点,否则肯定不优。
我们一定是把每个连通块炸成若干条链,最后再连起来,连接次数为炸掉的边数。
每一条边被炸掉都会分离出两个连通块,在后来需要用一条边连接以将连通块数减少 1 , 所以我们可以把代价归到第一类操作上。
- 删除一个点周围所有存在的边,其代价为删除的边数 + 1
1 表示本次操作代价,其它删除的边都会增加一次二类操作的代价。
然后就可以非常简单地 dp 了 , 发现子树特征信息只和这个点的状态相关,所以记录下这个点的状态即可。
表示炸掉这个点的,子树内形成若干链的最小代价。
表示不炸掉这个点,且保留父亲和他连边(即儿子内只能连最多一条边)的代价。
表示不炸掉这个点,且不保留父亲和他连边(即儿子内只能最多两条边)的代价。
初值:
, leaf 为叶子。
转移:
儿子可以选择炸或者不保留父亲之间的边,如果炸的话要减去父亲 -> 儿子这条连边,因为儿子炸掉后父亲不用炸了。
感觉这个转移用文字阐述比式子方便的多。
如果和父亲之间保留了连边,最多就只能和一个儿子保留连边,其它儿子都要炸掉。
如果和父亲之间保留了连边,最多就只能和两个儿子保留连边,其它儿子都要炸掉。
具体转移可以选择求出 的最大值和次大值,也可以选择做一个大小为 1 和 2 的背包 , 题解直接懒,使用了 sort , 故代码复杂度是 , 使用 nth_element 即可做到 。
代码(只放了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));
}
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970968,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步