CCF 201703-4 地铁修建_最小生成树

自己独立思考出这道题, 而且正解思路是一遍过, so 嗨皮.

贴一下思路历程, 思考的过程是很珍贵的. 对一个问题, 从陌生到有一个大致的了解, 其中会有错误的思路, 发现错误然后及时回头去想其他的方法. 有时有能做出结果的思路但不是最好的思路, 从差方法上进行横向拓展, 突然灵机一动, 想到另一种方法, 最后解答出来.

以下


初印象: 这像是一道DAG最长路或者说关键路径题目,但是又很大的不同.

因为不用修建所有隧道,所以正确的路径肯定是一条能够通往终点的无分支路径,答案就是所有这样路径里面需要时间最短的

那么有两种情况
  1. 这条路径可以根据某种规则直接选出来,然后求它的最短时间即可
  2. 不选出这条路径,而是遍历所有的这样的路径,求出其中的最短时间的最短的

看题目数据正解大概O(nlogn)一级别的复杂度
其中60%的数据可以是O(n^2)的时间复杂度.

先来考虑这个问题:有这样一条路径,如何确定修建它的最短时间?

—等等,似乎上面全错.一个公司只能修建一条隧道, 而且有n个公司那么足够修一条路了. 公司是不用选的.

只需要选择路径,并且路径里面边长最大的就是结果.

那么岂不是很简单?只要比较所有可达路径里面边数<=n的,其中最大耗时最小的就是结果了.

那么最优解可能是有一条边数>=n的路径嘛? 可以证明是不会的.因为会成环,成环就没有意义了. 所以不用担心人手不够

扫描二维码关注公众号,回复: 2751775 查看本文章

那么这些路, 每个顶点只会经过一次,可以方便地用dfs去遍历了. 每次步进到新的点,比较一下最大边,找出最小就ok

暴力DFS的程序提交了,TLE20分, 这果然不像单纯地一张图上对所有边进行遍历, 毕竟是会恢复状态的,这就是我之前潜意识里面认为的那种DFS是时间复杂度很高的情况.

不过思路应该没错,那么,是不是记忆化搜索呢?

是不是记忆化搜索我不知道,但我发现它可能是最小生成树!!!

先找到最小生成树路径, 然后依次把大的边删掉,看1是否还和n连通,当遇到第一个不能删的边时,这个就是答案了.

amazing, 因为它可以修n条路,那么铁定的最小生成树的n - 1条边都可以修好,那么1-n肯定是联通的.

既然如此,那就从树里面找出满足连通性质的最小最大边,根据贪心的思想,当然是从最大边开始删,而且他的短边

对结果是没有影响的,反正人手充足.那么用Kruskal的话,这部分是O(MlogM).

接下来的问题是: 如何删边.

又思考一下,发现与其删边,不如加边,直到加到第一个满足1和n连通的边.

perfect.

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

struct Edge {
    int from, to, w;
    Edge(int f, int t, int w) : from(f), to(t), w(w) {}
};

const int maxn = 100000 + 5;
int n, m, father[maxn];
vector<Edge> edge, edg;

bool cmp(Edge x, Edge y)
{
    return x.w < y.w;
}

int find(int x)
{
    return x == father[x] ? x : father[x] = find(father[x]);
}

void Kruskal()
{
    sort(edge.begin(), edge.end(), cmp);
    for (int i = 0; i < edge.size(); ++i) {
        int x = find(edge[i].from), y = find(edge[i].to);
        if (x != y) {
            father[x] = y;
            edg.push_back(edge[i]);
            if (edg.size() == n - 1) return;
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        father[i] = i;
    for (int i = 0, from, to, w; i < m; ++i) {
        cin >> from >> to >> w;
        edge.push_back(Edge(from, to, w));
    }
    Kruskal();
    for (int i = 1; i <= n; ++i)
        father[i] = i;
    for (int i = 0; i < edg.size(); ++i) {
        int x = find(edg[i].from), y = find(edg[i].to);
        if (x != y) {
            father[x] = y;
        }
        if (find(1) == find(n)) {
            cout << edg[i].w;
            return 0;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/wjh2622075127/article/details/81463435