CodeForces 1294F Three Paths on a Tree

Posted syksykccc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CodeForces 1294F Three Paths on a Tree相关的知识,希望对你有一定的参考价值。

Description

描述

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

输入

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

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

输出

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

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

样例

输入

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

输出

5
1 8 6

解释

技术图片

 

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

Solution

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

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

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

技术图片

交点为 $4$。

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

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

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

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

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

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

为什么呢?

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

技术图片

分两种情况:

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

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

技术图片

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

那么我们有:

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

技术图片

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

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

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

技术图片

我们还是设 $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) $$

技术图片

右边去掉一个 $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$ 就出来了。

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

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

技术图片

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

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

$ extbf{if } u ot in ext{path } extbf{then } f_u = maxlimits_{v in ext{sons(u)}} {f_v +1}$
$ extbf{else } ext{let } u ext{ be path}_i, f_u = maxlimits_{v in ext{sons(u)}, v ext{ 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 1294F Three Paths on a Tree的主要内容,如果未能解决你的问题,请参考以下文章

codeforces-1335-E Three Blocks Palindrome

codeforces 653A A. Bear and Three Balls(水题)

Divide by Three CodeForces - 792C

题解[CodeForces1154A]Restoring Three Numbers

Codeforces Round #485 (Div. 2) C. Three displays

CodeForces - 1131D(Three Integers)