POJ 1985 Cow Marathon & HDU 2196 Computer(树的直径)

74 篇文章 0 订阅
17 篇文章 0 订阅

题目链接;
POJ 1985 Cow Marathon
HDU 2196 Computer
题意:
两道题其实都是求树的直径。
分析:
树的直径:树中所有最短路径的最大值。
定理:

  • 在一个连通的无项无环图中,以任意结点出发所能到达的最远结点,一定是该图直径的端点之一。

证明:假设直径是 δ(s,e) ,任意结点为 x ,其最远能到达的结点为y。分两种情况:
①如果 x 是直径δ(s,e)上的结点,如果 y 不是s,e之一,即: xy>xs,xy>xe ,此时满足: ys=yx+xs>xe+xs=se ye=yx+xe=xs+xe=se ,这与直径是 δ(s,e) 不符。所以 y 必然是s,e之一。
②:如果 x 不是直径δ(s,e)上的结点,设 xy 路径上一结点 z 。如果z在直径 δ(s,e) 上,根据上面的证明可知 y 必然是s,e之一。如果 z 不在直径δ(s,e)上,即路径 xy 和直径 δ(s,e) 完全不相交,这时连接 sx,se ,易得: sy=sx+xy>sx+xe=se ,这又与 δ(s,e) 是直径相违,所以 y 也必然是直径s,e端点之一。
- 树的直径等于以树直径上任意一点为根的有根树,其左子树的高度+1,再加上其右子树高度+1。

下面的代码只是部分。

求树直径的方法:
1.根据定理1可以以任意结点出发找的其最远距离结点,这个结点必然是直径端点之一,再以这个结点出发找到求得其最远距离,可以使用 dfs bfs

void bfs(int u) //bfs版本,采用链式前向星存边
{
    queue<int> que;
    vis[u] = 1;
    que.push(u);
    while (!que.empty()) {
        int cur = que.front();
        que.pop();
        for (int i = head[cur]; i != -1; i = edge[i].next) {
            int v = edge[i].to, w = edge[i].w;
            if (vis[v]) continue;
            dp[v] = dp[cur] + w;
            vis[v] = 1;
            que.push(v);
        }
    }
}
int solve()
{
    memset(vis, 0, sizeof(vis));
    memset(dp, 0, sizeof(dp));
    bfs(1);
    int tmp = 0, st; //st是直径的一个端点
    for (int i = 1; i <= n; ++i) {
        if (dp[i] > tmp) {
            tmp = dp[i];
            st = i;
        }
    }
    memset(dp, 0, sizeof(dp));
    memset(vis, 0, sizeof(vis));
    bfs(st);
    tmp = 0;
    for (int i = 1; i <= n; ++i) {
        if (dp[i] > tmp) tmp = dp[i];
    }
    return tmp; //树的直径
}
void dfs(int u)  //dfs版本
{
    vis[u] = 1;
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to, w = edge[i].w;
        if (vis[v]) continue;
        dp[v] = dp[u] + w;
        dfs(v);
    }
}
//调用时只需要把上面bfs()版本中solve()函数bfs()改为dfs()

2.另外一种求树的直径的方法。我们可以想办法求出每个节点到其他节点的最远距离。这个最远距离可能来自节点的子树也可能来自节点的父亲节点。我们先用两个数组 far[] ffar[] 保存节点通过子树可以得到的最远距离和次远距离,并用 id[] 保存获得最远距离时该子树的根节点编号。假设节点 v 的父亲节点是u,对于 v u获得的最远距离 father[v] ,如果 id[u]=v ,那么只需要考虑 father[v]=max(father[u],ffar[u])+w w u v 的距离。如果id[u]!=v,那么 father[v]=max(father[u],far[u])+w 。两次处理都需要 dfs

//far[]最远子树距离,ffar[]次远子树距离,father[]通过父亲节点获得的最远距离
//id[]从子树获得最远距离时该子树的根节点编号

void dfs1(int u, int p) //p是u的父亲节点
{ //从子树获得最远距离和次远距离
    if (far[u] != -1) return ;
    far[u] = ffar[u] = 0;
    // 找子树最远距离
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to, w = edge[i].w;
        if (v == p) continue; //只能从u的子树获得,不能返回父亲节点
        dfs1(v, u);
        if (w + far[v] > far[u]) {
            far[u] = far[v] + w;
            id[u] = v;
        }
    }
    // 找子树次远距离
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to, w = edge[i].w;
        if (v == p || v == id[u]) continue; //不能是父亲节点和最远距离节点
        if (w + far[v] > ffar[u]) {
            ffar[u] = far[v] + w;
        }
    }
}

void dfs2(int u, int p) //p是u的父亲节点
{ //从父亲节点获得最远距离
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to, w = edge[i].w;
        if (v == p) continue; 
        if (v == id[u]) {  //如果v是u最远距离时的子树根节点
            father[v] = max(father[u], ffar[u]) + w;
        } else { 
            father[v] = max(father[u], far[u]) + w;
        }
        dfs2(v, u);
    }
}

//主函数部分
memset(far, -1, sizeof(far));
memset(ffar, -1, sizeof(ffar));
memset(father, -1, sizeof(father));
dfs1(1, 0); //对每个点求其到子树上节点的最远距离和次远距离
father[1] = 0;
dfs2(1, 0); //对每个点求其经过父节点可到达的最远距离
int diameter = 0;
for (int i = 1; i <= n; ++i) {
    longest[i] = max(father[i], far[i]); //longest[i]时节点i可以到达的最远距离
    if (longest[i] > diameter) diameter = longest[i];
    //printf("longest[%d] = %d\n", i, longest[i]);
}
printf("%d\n", diameter);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值