【每日一题】Shortest Path

Shortest Path

https://ac.nowcoder.com/acm/problem/13886


题目

题目描述: 
今天,HH成为一名设计师,他面临一个问题,因此他要求您提供帮助。
Treeisland是一个拥有n个城市和n-1条双向道路的国家,您可以从任何城市前往任何其他城市。
设计人员将设计一个计划,将n个城市分为n / 2对,以使n / 2对城市之间的长度之和最小。
现在,HH已完成它,但他不知道这是否正确,因此他想和您一起计算。
保证n为偶数。

输入描述:
 第一行包含一个正整数T(1≤T≤100),表示有T个测试用例。 
对于每个测试的情况:第一行包含一个正整数n(1≤n≤10 4),代表城市在Treeisland数,它的保证n是偶数。 
然后跟随n-1行。每行有三个正整数U,V和Len,(U≠V,1≤u≤n,1≤v≤n,1≤len≤109 指示存在u和v之间len个长度的道路)。 
 这保证您可以从任何城市到达任何城市。

输出描述:
对于每个测试用例,一行输出一个整数,代表最小长度之和。


解析

这道题没有复杂的算法,只是单纯的dfs,但是需要思考。
  • 我们先来简单描述一下这道题:我们每两个点组成一组,取得他们之间的长度。最后求最短长度总和

算法讲解:
  1. 刚看到的时候,我觉得无从下手,因为又是一道求啥最短长度和的题目,吓到我人都懵掉了。看到邓老师讲解后豁然开朗。
  2. 我们要取得最短长度的话,我们就要尽量不重复选边(重复会造成资源浪费)

    就像这个,你不选左边的,硬是要选右边的岂不是有点毛病。
  3. 所以在不重复选边的思考下,我们发现,每条边只有两种可能:选或不选!那么我们只要知道某条边什么时候选就行了。
    由这点我们就发现,我们不管怎么选边都没事,最后的边和一定的。
  4. 那么我们对着某一个节点来考虑这个问题:某一个节点的选择一般都是最近的节点(父节点),或者在已占用的情况下找一个远一点的(兄弟节点)
  5. 但是为什么是父节点和兄弟节点呢?让我们来看下面这张图:

  6. 在这张图里面我们可以发现:左边三个节点无法内部消化,兄弟可以配在一起(父节点可以找前面的)。而个节点的可以直接成双内部消化了
  7. 所以我们得到的结论就是:当子树为奇数的时候,根节点一定会和前面的点相连(对叶节点同样适用,叶节点是奇数树,而且一定连边)。

操作方法:
  1. 知道算法后,操作就是其次比较难的地方了。在这个dfs里面我们要先明确我们的工作是什么。
  2. <1>我们要知道以某一点为根节点的子树大小(用于判断是否可内部消化)。<2>我们要得到我们的最小权和
  3. 首先计算子树大小,我们在这里运用分治递归的思路,从小到大进行合并:我们用一个变量(cnt)累加根节点的每一条支路的节点数。
  4. 然后就是得到最小权和:我们可以在递归的时候添加一个变量用于保存当前路径(当前根节点和其父节点)的权值,是奇数就加上就好了(原因见算法讲解)

打代码咯:
  1. 首先是老操作,前向星保存树形结构。
  2. 然后就可以进行我们惊险刺激的dfs了,这里唯一要注意的就是我们没有确定的根,随便选一个就好了(权值为啥都一样,反正总数是偶数也不用在意,我们还是填0)
  3. 看代码吧~


AC代码

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
//代码预处理区

const int MAX = 1e5 + 7;
int head[MAX], tot;
struct Node {
    int v, w, next;
} edge[MAX << 1];
ll ans;
//全局变量区

template<class T>inline void read(T& res);
void add_edge(int u, int v, int w);
int dfs(int u, int fa, int w);
//函数预定义区

int main() {
    int T; read(T);
    while (T--) {
        int n; read(n);
        tot = 0; memset(head, 0, sizeof head);
        for (int i = 1; i <= n - 1; i++) {
            int u, v, w; read(u); read(v); read(w);
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
        ans = 0;
        dfs(1, 0, 0);
        printf("%lld\n", ans);
    }
    return 0;
}
template<class T>inline void read(T& res) {
    char c; T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
void add_edge(int u, int v, int w) {
    tot++;
    edge[tot].v = v;
    edge[tot].w = w;
    edge[tot].next = head[u];
    head[u] = tot;
}
int dfs(int u, int fa, int w) {
    int cnt = 1;//初始化为1表示计算自己这个点
    for (int i = head[u]; i; i = edge[i].next)
        if (edge[i].v != fa)
            cnt += dfs(edge[i].v, u, edge[i].w);
    //计算子树的总节点数
    if (cnt & 1) ans += w;
    //当前树节点为奇数则要加上与父节点的连接
    return cnt;
}
//函数区
每日一题 文章被收录于专栏

憨憨的专栏

全部评论

相关推荐

ProMonkey2024:5个oc?厉害! 但是有一个小问题:谁问你了?😡我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了(别的帖子偷来的,现学现卖😋)
点赞 评论 收藏
分享
shtdbb_:还不错,没有让你做了笔试再挂你
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务