关于直径中点性质的一些疑问
  • 板块学术版
  • 楼主FDsama
  • 当前回复2
  • 已保存回复2
  • 发布时间2024/4/25 19:10
  • 上次更新2024/4/25 21:07:50
查看原帖
关于直径中点性质的一些疑问
1057308
FDsama楼主2024/4/25 19:10

感性理解出来这么一个东西:
给定一棵非负权树,对于任意一点,一定存在一条以它为起点的最长链过直径中点(这里的中点可以是边)

用反证法推一下好像是对的:
如果以直径中点为根(是边的话可以分类讨论两个点),那么若有一个点的最长链全部都不经过根,那么如果把这条链连到根直径就会变长肯定不对
并且这个结论推广到直径任何一点上好像都是对的

那么是否可以这样解决这个问题:

给定一棵非负权树,求出每个结点与相距最远结点间的距离

先求出直径并找到夹住直径中点的两个点,然后分别对两个点的子树dfs,求出到这两个点的距离再加上另一侧直径的长度

这里有一份通过了某OJ该问题的代码
但是这个OJ数据比较水,能否有大佬帮我看一下这个做法是否正确,如果不正确能否给出一个Hack? 真的感谢喵~

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

//自己平常用的奇怪码风,如果看不懂那真的很抱歉喵
class FD {
private:
    inline static int Read() {
        int r = 0, f = 0, c = getchar();
        while((c < '0' || c > '9') && ~c) f |= c == '-', c = getchar();
        while(c >= '0' && c <= '9') r = (r << 1) + (r << 3) + (c ^ 48), c = getchar();
        return f ? -r : r;
    }

    typedef long long ll;
    static constexpr int AwA = 1e4 + 10;

    //链式前向星存树
    struct Edge {
        int nxt, v, w;
    } e[AwA << 1];
    int head[AwA], ecnt;
    inline void AddEdge(int w, int u, int v) {
        e[++ecnt] = { head[u], v, w }, head[u] = ecnt;
        e[++ecnt] = { head[v], u, w }, head[v] = ecnt;
    }

    //这道题有多测
    inline void Init() {
        memset(head + 1, 0, sizeof(int) * n);
        ecnt = 0;
    }

    //dfs求直径,同时算距离+求fa
    void Dfs(int u, int _fa) {
        fa[u] = _fa;
        for(int i = head[u], v; i; i = e[i].nxt) {
            v = e[i].v;
            if(v == fa[u]) continue;
            dis[v] = dis[u] + e[i].w;
            Dfs(v, u);
        }
        if(dis[u] > dis[tar]) tar = u;
    }

    //对于子树内进行答案计算
    void GetAns(int u, int _fa) {
        fa[u] = _fa;
        for(int i = head[u], v; i; i = e[i].nxt) {
            v = e[i].v;
            if(v == fa[u]) continue;
            dis[v] = dis[u] + e[i].w;
            GetAns(v, u);
        }
        ans[u] = dis[u] + len;
    }

    //n为树上节点数,m为直径节点数,tar为dfs求直径的辅助变量
    int n, m, tar;
    int fa[AwA];
    //存直径上的节点
    int sq[AwA];
    ll dis[AwA], ans[AwA];
    //直径另一半的长度
    ll len;
public:
    inline void Main() {
        while((n = Read())) {
            Init();
            //压行特有的倒序Read
            for(int i = 2; i <= n; i++) AddEdge(Read(), Read(), i);

            //求直径
            tar = 0;
            dis[1] = 0;
            Dfs(1, 0);

            dis[tar] = 0;
            Dfs(tar, 0);

            //找出直径节点
            m = 0;
            for(int x = tar; x; x = fa[x]) sq[++m] = x;

            //找到直径中点两侧的点,为sq[k]和sq[k-1]
            int k = 0;
            for(int i = m; i && !k; i--)
                if(dis[sq[i]] * 2 <= dis[tar] && dis[sq[i - 1]] * 2 >= dis[tar]) k = i;

            //对两个点包含子树分讨
            len = dis[tar] - dis[sq[k]];
            dis[sq[k]] = 0;
            //不要让这个点走到另一个点的子树上
            GetAns(sq[k], sq[k - 1]);

            //另一个点同理
            len = dis[sq[k - 1]];
            dis[sq[k - 1]] = 0;
            GetAns(sq[k - 1], sq[k]);

            for(int i = 1; i <= n; i++) printf("%lld\n", ans[i]);
        }
    }
} Fd;

int main() {
    Fd.Main();
    return 0;
}
2024/4/25 19:10
加载中...