CodeForces 1294F Three Paths on a Tree

Description

描述

给一棵 $n$ 个点的树,找到三个点,使得两两之间,共三条路径,所经过的边的并集尽可能大。

输入

第一行为一个正整数 $n$($3 \le n \le 2 \times 10^5$)。

接下来 $n -1 $ 行,每行两个数 $u,v$ 表示一条边($1 \le u, v \le n$,$u \neq v$)。

输出

第一行为一个数 $res$,表示并集的大小。

第二行为三个数 $a, b, c$,表示找的三个点,输出任意解($a \neq b$,$b \neq c$,$a \neq c$)。

样例

输入

8
1 2
2 3
3 4
4 5
4 6
3 7
3 8

输出

5
1 8 6

解释

CodeForces 1294F Three Paths on a Tree

 

 这是另一种可行解。$1 \to 5$ 经过 $(1, 2)$,$(2, 3)$,$(3, 4)$,$(4, 5)$;$1 \to 6$ 经过 $(1, 2)$,$(2, 3)$,$(3, 4)$,$(4, 6)$;$5 \to 6$ 经过 $(4,5)$,$(4,6)$。并集是 $(1, 2)$,$(2, 3)$,$(3, 4)$,$(4, 5)$,$(4, 6)$ 共 $6$ 条边。

Solution

介绍一种自己的乱搞做法。

首先设选取的三个点分别为 $du, dv, dw$。

我们可以发现,三条路径的并集可以看作是有唯一的一个交点,例如样例:

CodeForces 1294F Three Paths on a Tree

交点为 $4$。

那么,问题就变成了,找到一个 $u$ 作为交点,$du, dv, dw$ 一定是到 $u$ 距离最远的,且不在同一棵子树里 的三个点。

为什么不在同一棵子树里?

因为如果 $du, dv$ 在一棵子树里,显然,我们可以找到它们分叉的地方,比如叫做 $v$,那 $u \to v \to du$ 和 $u \to v \to dv$ 两条路径就有 $u \to v$ 这一段相交的地方,那么交点就大可以设成 $v$ 了。

于是 $\mathcal O(n^2)$ 的做法不难想到:枚举 $u$,$n$ 次 BFS,暴力求出最远点更新答案。

这个做法显然是会 TLE 的,正解需要下面这个结论:

$du, dv$ 一定是树的直径的两个端点。

为什么呢?

比如我们有这么一棵树,它的直径用红色标出。

CodeForces 1294F Three Paths on a Tree

分两种情况:

  • 枚举到的 $u$ 在直径上。

那么我们要证明的就是最远的两条链 $du, dv$ 一定是树的直径的端点。

CodeForces 1294F Three Paths on a Tree

比如我们枚举到了这样的 $u$,我们发现 $u$ 除了直径还有一棵子树,这个里面离 $u$ 最远的叫做 $w$。

那么我们有:

$$ dis(u, a) \ge dis(u, w) \cdots \cdots (1) \\ dis(u, b) \ge dis(u, w) \cdots \cdots (2) $$

CodeForces 1294F Three Paths on a Tree

$(1)$ 是因为,如果有 $dis(u, w) > dis(u, b)$ 了,那么树的直径就不可能是 $a \to b$,而是 $a \to w$ 了;$(2)$ 也同理可证。

那么,$u$ 除了子树里面的点,也就 $a, b$ 两个选择了,所以 $du = a$,$dv = b$。

  • 枚举到的 $u$ 不在直径上。

CodeForces 1294F Three Paths on a Tree

我们还是设 $u$ 的子树(这次有两棵)离 $u$ 最远的点为 $w$。再设 $u$ 不断沿着父亲追溯到直径上的点为 $v$。

同上,可以得到:

$$ dis(v, a) \ge dis(v, w) = dis(u, v) + dis(u, w) \\ dis(v, b) \ge dis(v, w) = dis(u, v) + dis(u, w) $$

CodeForces 1294F Three Paths on a Tree

右边去掉一个 $dis(u, v)$ 不影响不等式,所以:

$$ dis(v, a) \ge dis(u, w) \\ dis(v, b) \ge dis(u, w) $$

左边再加上一个 $dis(u, v)$ 也不影响不等式,所以:

$$ dis(v, a) + dis(u, v) = dis(u, a) \ge dis(u, w) \\ dis(v, b) + dis(u, v) = dis(u, b) \ge dis(u, w) $$

所以我们还是会先选 $du = a$,$dv = b$!

然后既然这样了,我们就可以先找到直径,于是 $du, dv$ 就出来了。

然后记录直径上的点为 $\text{path}$,在 $\text{path}$ 上枚举 $u$,找到「不在直径上」且 「距离 $u$ 最远」的点 $w$,用来更新答案。

具体怎么实现呢?我们可以把直径拉出来,比如样例,$1 \to 5$ 是直径,那么我们就:

CodeForces 1294F Three Paths on a Tree

把直径拉出来,然后我们从上至下记录为 $\text{path}_{1 \to len}$,我们发现,只要 $\text{path}_i$ 的儿子不是 $\text{path}_{i + 1}$,就是可以更新的。

容易设计出一个树形 DP,$f_{u}$ 表示 $u$ 最佳的长度,$g_{u}$ 表示来源,显然:

$\textbf{if } u \not \in \text{path } \textbf{then } f_u = \max\limits_{v \in \text{sons(u)}} \{f_v +1\}$
$\textbf{else } \text{let } u \text{ be path}_i, f_u = \max\limits_{v \in \text{sons(u)}, v \text{ isn't path}_{i + 1} }\{ f_v +1\}$

$g_u$ 相应更新。

时间复杂度 $\mathcal O(n)$,代码如下,仅供参考:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, dep[N], fa[N], du, dv, dlen;
vector<int> G[N];
void dfs(int u) // 预处理
{
    for(int v : G[u]) if(v ^ fa[u])
    {
        dep[v] = dep[u] + 1;
        fa[v] = u;
        dfs(v);
    }
}
int tmp[N], cur, path[N];
void find_path(int u) // 拉出直径
{
    tmp[++cur] = u;
    if(u == dv)
    {
        for(int i = 1; i <= cur; i++) path[i] = tmp[i];
        return;
    }
    for(int v : G[u]) if(v ^ fa[u]) find_path(v);
    cur--;
}
int f[N], g[N], maxl = -1, dw;
void solve(int u, int pos) // DP
{
    if(u ^ path[pos])
    {
        for(int v : G[u]) if(v ^ fa[u])
        {
            solve(v, pos);
            if(f[u] < f[v] + 1)
                f[u] = f[v] + 1, g[u] = g[v];
        }
    }
    else
    {
        for(int v : G[u]) if(v ^ fa[u])
        {
            solve(v, pos + 1);
            if(v ^ path[pos + 1] && f[u] < f[v] + 1)
                f[u] = f[v] + 1, g[u] = g[v];
        }
    }
    if(f[u] > maxl && g[u] != du && g[u] != dv) // 注意,如果来源是 du 或 dv 就重复了,所以不行
        maxl = f[u], dw = g[u];
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dep[1] = fa[1] = 0;
    dfs(1);
    for(int i = 1; i <= n; i++) if(dep[i] > dep[du]) du = i;
    dep[du] = fa[du] = 0;
    dfs(du);
    for(int i = 1; i <= n; i++) if(dep[i] > dep[dv]) dv = i;
    dlen = dep[dv];
    find_path(du);
    cur = n;
    for(int u = 1; u <= n; u++) g[u] = u;
    solve(du, 1);
    cout << maxl + dlen << endl;
    cout << du << ' ' << dv << ' ' << dw << endl;
    return 0;
}
上一篇:Codeforces Round #615 (Div. 3) F. Three Paths on a Tree(树的直径,dfs)


下一篇:前端跨域配置