题目链接

树的直径

题意:在一棵树上加1条或者2条边使得在保证每条边都遍历了的情况下,回到原点1。问加边后总的花费时间最少是多少,一条边花费的时间为1.

加一条边显然在最长链的两个端点之间加一条边就是最省时间的。
如果要加两条边,其中一条还是连最长链,因为每一条边都得遍历,所以第二条边显然不能越过太多的边,要不然还得倒回去遍历一遍。在已经形成一个环的情况下,再加一条边,形成另一个环,这个环只会有两种情况,第一种是有一些边是和第一个环重合的,另一种情况则没有,对于和第一个环重合的部分,我们可以理解为原本只用走一次,但是为了走第二个环,那重合的部分,我们还得退回去走另外一个环,重合的部分仍然得走两次,和原来不变。我们把第一个环所有边的权值设置为-1,再求一次最长链,求出来的是减去与第一个环重合的最长链,两次最长链的和-2(两条新边)就是加上两条边后省下的时间。

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <vector>
#include <cmath>
 
using namespace std;
 
typedef long long ll;
 
const int MaxnN = 1e5+10;
const int MaxnM = 1e6+10;
const int INF = 0x3f3f3f3f;
const ll LINF = 1e18;
const double eps = 1e-6;
struct Edge {
    int u, v, w, next;
} edge[MaxnM];
int h[MaxnN], edge_cnt;
int n, k, rt, max_len;
int son_1[MaxnN], son_2[MaxnN], dp[MaxnN]; 
// son_1,son_2分别存储当前节点第一长链和第二长链的边,用于对长链做操作 
void add(int u, int v) {
    edge[edge_cnt].u = u;
    edge[edge_cnt].v = v;
    edge[edge_cnt].w = 1;
    edge[edge_cnt].next = h[u];
    h[u] = edge_cnt++;
}
void dfs(int u, int fa) {
    for(int i = h[u]; i != -1; i = edge[i].next) {
        int v = edge[i].v;
        if(v != fa) {
            dfs(v, u);
            if(max_len < dp[u]+dp[v]+edge[i].w) {
                max_len = dp[u]+dp[v]+edge[i].w;
                rt = u;
            }
            if(son_1[u] == -1 || dp[v] > dp[edge[son_1[u]].v]) {
                son_2[u] = son_1[u];
                son_1[u] = i;
            } else if(son_2[u] == -1 || dp[v] > dp[edge[son_2[u]].v]) {
                son_2[u] = i;
            }
            dp[u] = max(dp[u], dp[v]+edge[i].w);
        }
    }
}
int main(void)
{
    scanf("%d%d", &n, &k);
    int u, v;
    for(int i = 1; i <= n; ++i) h[i] = -1;
    edge_cnt = 0;
    for(int i = 0; i < n-1; ++i) {
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    int ans = 2*(n-1);
    for(int i = 1; i <= n; ++i) son_1[i] = son_2[i] = -1;
    for(int i = 1; i <= n; ++i) dp[i] = 0;
    max_len = 0;
    dfs(1, -1);
    ans -= max_len-1;
    if(k > 1) {
        for(int i = son_1[rt]; i != -1; i = son_1[edge[i].v]) {
            edge[i].w = edge[i^1].w = -1;
        }
        for(int i = son_2[rt]; i != -1; i = son_1[edge[i].v]) {
            edge[i].w = edge[i^1].w = -1;
        }
        for(int i = 1; i <= n; ++i) son_1[i] = son_2[i] = -1;
        for(int i = 1; i <= n; ++i) dp[i] = 0;
        max_len = 0;
        dfs(1, -1);
        ans -= max_len-1;
    }
    printf("%d\n", ans);
    return 0;
 }