2020暑期D2-C 思路+证明
主为自用,欢迎指正。

https://ac.nowcoder.com/acm/contest/5667/C

前置知识:
树中链的覆盖,节点A到节点B的经过尽可能少的点的路径(最短路径),亦可以理解成一种遍历经过的路径,经过的点和边都是覆盖。
dfs序,树在dfs遍历时,树的节点出入栈时的序列。

题意:
一颗具有n个节点的树,问是否存在尽可能少的链覆盖所有的节点。

思路:
设s为叶结点数,则连最少存在s/2条,向下取整,可以简单理解链的端点取相邻的节点,路径将最短,即容易出现无法经过祖父节点的情况。结论一,链经过的节点应该尽可能多。
故端点的选择间距尽可能大,容易想到二分叶子节点,将叶子节点分为左右两部分,左边第一个节点与右边第一个节点成链。结论二,链的端点为间隔mid的叶子节点,则可经过尽可能多的节点。(mid=s/2)
若叶子节点s为偶数,则恰好二分,若为奇数,则剩余叶子节点(mid+mid+1)可随意找一个节点成链,推荐根节点(根节点为理论上最难经过的点)。结论三,叶子节点数若为奇数,则让剩余叶子节点与根节点成链。
由于题意需要输出成链的端点,故可以用dfs序找到叶子节点的位置。叶子节点在dfs序中是相邻的数。

代码步骤:

AC代码:

#include <bits/stdc++.h>
using namespace std;

int n;
vector <int> g[200005], leap;

void dfs(int pos, int fa) {
    if(g[pos].size() == 1) {leap.push_back(pos); return;}
    for(int i = 0;i < g[pos].size(); i++) {
        if(g[pos][i] == fa) continue;
        dfs(g[pos][i], pos);
    }
}

int main() {
    scanf("%d", &n);
    int u, v, head = 0;
    for(int i = 1;i < n; i++) {
        scanf("%d %d", &u, &v);
        if(!head) head = u;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    for(int i = 1;i <= n; i++) {
        if(g[i].size() > 1) {head = i; break;}
    }

    dfs(head, -1);
    int mid = ((int)leap.size()+1)/2;
    printf("%d\n", mid);

    for(int i = 0;i < mid; i++) {
        if(i+mid >= (int)leap.size()) printf("%d %d\n", leap[i], leap[mid]);
        else printf("%d %d\n", leap[i], leap[i+mid]);
    }
}

简单图证:

注: