1 条题解
-
0
自动搬运
来自洛谷,原作者为

xieyikai2333
此时此刻的光辉,盼君勿忘。搬运于
2025-08-24 22:40:31,当前版本为作者最后更新于2022-10-23 21:09:38,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
闲话
- 赛时爆肝 T2,结果 T2 是个紫的没打出来,然后这题没时间了,打了个 分的暴力滚蛋了。
解题思路
-
显然,我们先进行操作 1 再进行操作 2 一定不劣,否则操作 2 连上去的边可能又被炸掉。
-
考虑把森林炸成若干链,然后连起来,显然如果我们炸了 条边,那么操作 2 的次数可以表示为 。于是我们只要使炸掉的边数()+炸掉的点数(操作 1 的次数)最小即可。
-
考虑对于每个无根树随便取个根进行树形DP。
状态设计
- 设 表示 的子树已经被炸成若干链了且 $\begin{cases} \text{这个点被炸了} &0\\ \text{这个点没有被炸,它的儿子有 1 个连着它} &1\\ \text{这个点没有被炸,它的儿子有 2 个连着它} &2 \end{cases}$
状态转移
-
约定:记 为点 的儿子集合, 为点 的度数, 是一类数中的最大值, 是一类数中的次大值。
-
对于状态 :
- 转移方程:$$dp_{u,0}=\sum_{v \in son_{u}} \min\{dp_{v,0}-1,dp_{v,2}\}+d_{u}+1 $$
- 解释说明:
- 如果一个点被炸掉,那么它的子树无论何种情况都可以转移。
- 之所以 要减去 是因为如果 和 都炸它们之间的边被重复计算了。
- 之所以不用考虑 ,是因为 一定大于 ,原因由后面会给出的 的转移方程可知。
- 加上 是因为又炸了 条边,加上 是因为又炸了一个点
-
对于状态 :
- 转移方程:$$dp_{u,1}=(\sum_{v \in son_{u}} dp_{v,0})-\max\{0,FIR_{v \in son_{u}}(dp_{v,0}-dp_{v,1})\} $$
- 解释说明:
- 由定义, 的儿子最多有一个不被炸,所以最多有一个儿子状态可以是 其他都必须被炸。
-
对于状态 :
- 转移方程:$$dp_{u,2}=dp_{i,1}-\max\{0,SEC_{v \in son_{u}}(dp_{v,0}-dp_{v,1})\} $$
- 解释说明:
- 由定义, 的儿子最多有两个不被炸。在有一个不被炸的基础上再减去第二大的即可。
- 由转移方程可知, 显然成立。
最终答案
- 约定: 是最终答案, 是每棵树的根节点组成的集合。
至于为什么不用管 ,不用我多说了吧?
代码实现
好像没什么要注意的细节。。。
AC 代码:
#include <bits/stdc++.h> using namespace std; const int N=2e6+5; int dp[N][3]; bool vis[N]; vector<int> nodes[N]; void dfs(int u) { vis[u]=true; int fir=0,sec=0; for(int v:nodes[u]) { if(vis[v])continue; dfs(v); int delta=dp[v][0]-dp[v][1]; if(delta>fir)sec=fir,fir=delta; else if(delta>sec)sec=delta; dp[u][0]+=min(dp[v][0]-1,dp[v][2]); dp[u][1]+=dp[v][0]; } dp[u][0]+=nodes[u].size()+1; dp[u][1]-=fir; dp[u][2]=dp[u][1]-sec; return; } int main() { int n,m; scanf("%d %d",&n,&m); int ans=(n-1)-m; for(int i=1;i<=m;i++) { int u,v; scanf("%d %d",&u,&v); nodes[u].push_back(v); nodes[v].push_back(u); } for(int i=1;i<=n;i++)if(!vis[i])dfs(i),ans+=min(dp[i][0],dp[i][2]); printf("%d",ans); return 0; }
- 1
信息
- ID
- 7688
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者