CF911F Tree Destruction

题意:

给你一棵 n 个结点组成的树,你需要对树进行 n1 次操作,一次操作包含如下的步骤:

  1. 选择两个叶子结点
  2. 将这两个结点之间简单路径的长度加到答案中
  3. 从树上删去两个叶子结点之一
    初始答案为 0,显然在 n1 次操作之后树上只剩下一个结点。
    计算最大的答案,并构造一组操作序列。

思路:

构造题……向来是我不拿手的。
浅析一下本人的思考过程。一开始想的是根据结点的深度进行构造,统计出每个深度的结点个数,然后枚举最大和次大的两个深度组合出最长的路径,优先割掉个数较少的深度的点。似乎可以统计出答案,但问题在于你并不知道其中的某两个结点是不是在同一棵子树内,也就不能通过直接计算深度来求两点距离。如果加lca,时间复杂度又炸了。。。

那换一个思路,在一棵树上,且是一条距离最大的路径,似乎与树的直径有关系?
先回顾一下树的直径的一些性质:

  • 对任一一个点来说,与它距离最远的点一定是树的直径的某个端点。
  • 对于两棵树,如果第一棵树的直径的两端点为 (x,y),第二棵树的直径的两端点为 (u,v),那么将两棵树连起来,新树的直径的两个端点一定是四个点中的其中两个点。
  • 对于一棵树,增删一个叶子结点,最多改变直径的一个端点。
  • 如果一棵树有多条直径,那么所有直径必交于一点。
  • 任一两条直径一定有且仅有一个极长连续段重合。

对于这道题来说,第一个性质是最重要的。它为方案的构造指明了思路。

可以先两遍 dfs 找出树的一条直径,然后将所有不在直径上的点与与它距离最远的一个直径的端点配对,再把这个点删除。
对于在直径上的点,等把所有不在直径上的点全部删除后,再挨个删掉即可。

这里有个细节,当你在处理没在直径上的点,求哪个端点与与它最远时,还要记录一下这个没在直径上的点和另一个端点的lca,这样才能求距离。算是一个比较有用的技巧吧,在代码里会展示。

#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 2e5; int n,pos[MAXN + 5][2],dp[MAXN + 5][2],tot,head[MAXN + 5],dep[MAXN + 5],S,T,ans; bool od[MAXN + 5];//是否在直径上 vector<pair<int,int> > out; struct EDGE{ int u,v,next; }edge[2 * MAXN + 5]; void add(int u,int v){ ++tot; edge[tot] = (EDGE){u,v,head[u]}; head[u] = tot; } void dfs(int u){//求直径 for(int i = head[u]; i; i = edge[i].next){ int v = edge[i].v; if(dep[v] < dep[u])continue; dep[v] = dep[u] + 1; dfs(v); } } void dfs1(int u){ for(int i = head[u]; i; i = edge[i].next){ int v = edge[i].v; if(dep[v] > dep[u]){ dfs1(v); od[u] = od[u] || od[v];//标记是否在直径上 } } } void dfs2(int u,int root){ for(int i = head[u]; i; i = edge[i].next){ int v = edge[i].v; if(dep[v] > dep[u])dfs2(v,(od[v] ? v : root));//如果访问到深度较当前更大的点(即父结点),假如这个点是在直径上的,那么之后从这个点出发的、不在直径上的点与直径的其中一个端点的lca就深了一层;如果不在直径上,则对lca无影响。(可以画个图想一想) } if(!od[u]){ if(dep[u] > dep[u] + dep[T]-(dep[root]<<1)){//比较到两个端点的距离 ans += dep[u]; out.push_back(make_pair(S,u)); } else{ ans += dep[u] + dep[T] - (dep[root]<<1); out.push_back(make_pair(T,u)); } } } signed main(){ scanf("%lld",&n); for(int i = 1; i < n; i++){ int u,v; scanf("%lld%lld",&u,&v); add(u,v); add(v,u); } memset(dep,0x3f,sizeof dep); dep[1] = 0; S = 1; dfs(S); for(int i = 2; i <= n; i++){ if(dep[i] > dep[S])S = i; } memset(dep,0x3f,sizeof dep); dep[S] = 0; T = S; dfs(S); for(int i = 1; i <= n; i++){ if(dep[i] > dep[T])T = i; } od[T] = 1; dfs1(S); dfs2(S,S); int now = T; while(now != S){ ans += dep[now]; for(int i = head[now]; i; i = edge[i].next){ int v = edge[i].v; if(od[v] && dep[v] < dep[now]){ out.push_back(make_pair(S,now)); now = v; break; } } } cout << ans << "\n"; for(int i = 0; i < out.size(); i++){ cout << out[i].first << " " << out[i].second << " " << out[i].second << "\n"; } }

__EOF__

本文作者Never Gonna Give You Up!
本文链接https://www.cnblogs.com/CZ-9/p/17367748.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   腾云今天首飞了吗  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示