zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    hive之Json解析(普通Json和Json数组)
    Oracle 默认时间格式 & Date格式转换
    Oracle 中的Top写法
    cvc-complex-type.2.4.a: Invalid content was found starting with element 'async-supported'.
    如何使用MyEclipse的快捷键查找文件和类等资源
    同时运行多个TOMCAT时配置修改
    Dmaven.multiModuleProjectDirectory system propery is not set.
    Java常见异常
    java异常处理中的return和throw
    有return的情况下try catch finally的执行顺序
  • 原文地址:https://www.cnblogs.com/syksykCCC/p/CF1294F.html
Copyright © 2011-2022 走看看