CF911F Tree Destruction

发布时间 2023-05-02 15:22:00作者: 腾云今天首飞了吗

题意:

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

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

思路:

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