题目链接

题目

题目描述

a180285非常喜欢滑雪。他来到一座雪山,这里分布着M条供滑行的轨道和N个轨道之间的交点(同时也是景点),而且每个景点都有一编号i(1 ≤ i ≤ N)和一高度Hi。a180285 能从景点i 滑到景点j 当且仅当存在一条i 和j 之间的边,且i 的高度不小于j。

与其他滑雪爱好者不同,a180285喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。于是a180285拿出了他随身携带的时间胶囊。

这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是 a180285 滑行的距离)。

请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间 之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。

现在,a180285站在1号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间 胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前 提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?

输入描述

输入的第一行是两个整数N,M。
接下来1行有N个整数Hi,分别表示每个景点的高度。
接下来M行,表示各个景点之间轨道分布的情况。每行3个整数,Ui,Vi,Ki。表示编号为Ui的景点和编号为Vi的景点之间有一条长度为Ki的轨道。

输出描述

输出一行,表示a180285最多能到达多少个景点,以及此时最短的滑行距离总和。

示例1

输入

3 3 
3 2 1 
1 2 1 
2 3 1 
1 3 10

输出

3 2

备注

对于30% 的数据,\(1 \le n \le 2000\)
对于100% 的数据,\(1 \le n \le 10^5\) 。对于所有的数据,保证 \(1 \le m \le 10^6\) , \(1 \le h_i \le 10^9\)\(1 \le k_i \le 10^9\)

题解

知识点:DFS,最小生成树。

显然,起点连通的景点都是需要到达的,因此先DFS一遍求多少景点可以到达。

建图时,要保证边的方向是高到低的,且记录较低点的高度方便之后排序。

因为有胶囊可以随意传送,所以原题实际上就是最小生成树了,但生成的方式不太一样。由于,景点只能从高到底走,换句话说,如果高的景点走不到的话,那么其后低的景点也是不能走到的。如果单纯根据边权排序,可能会出现 高->低->高 的情况,实际上这是不合法的。

因此,先按照高度为第一关键字从高到底排序,再按照边权为第二关键字从小到大排序。选边时,边的两端应该要和起点连通,这在第一步DFS时可以记录起点是否能达到 \(vis\) ,其次和普通最小生成树一样两端点目前处在不同连通块内才需要选边。

时间复杂度 \(O(n+m(\log m+\log n)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int h[100007];
vector<int> g[100007];
struct edge {
    int u, v, w, h;
}e[1000007];

int cnt = 0;
bool vis[100007];
void dfs(int u) {
    vis[u] = 1;
    cnt++;
    for (auto v : g[u]) {
        if (!vis[v]) dfs(v);
    }
}

int fa[100007];
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
    fa[find(x)] = find(y);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n;i++) cin >> h[i];
    for (int i = 1;i <= m;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = { u,v,w ,min(h[u],h[v]) };///短的那个是出点
        if (h[u] >= h[v]) {
            g[u].push_back(v);
        }
        if (h[u] <= h[v]) {
            g[v].push_back(u);
        }
    }
    dfs(1);
    for (int i = 1;i <= n;i++) fa[i] = i;
    sort(e + 1, e + 1 + m, [&](const edge &a, const edge &b) {
        return a.h > b.h ? 1 : a.h == b.h ? a.w < b.w : 0;
    });///因为是有向图,不确定滑道的较高点是否能走到。因此先把滑道出点较高的取了,保证滑道出点较矮的取的时候较高点一定走到了。
    ll sum = 0;
    for (int i = 1;i <= m;i++) {
        auto [u, v, w, h] = e[i];
        if (!vis[u] || !vis[v]) continue;
        if (find(u) == find(v)) continue;
        sum += w;
        merge(u, v);
    }
    cout << cnt << ' ' << sum << '\n';
    return 0;
}