题解【LOJ10068】「一本通 3.1 练习 3」秘密的牛奶运输

题面

题目就是要我们求出图中严格次小生成树的边权之和。

我们首先可以发现一个很显然的性质:严格次小生成树与最小生成树有且只有一条边不同。

我们先求出图的最小生成树,然后枚举每一条非树边,尝试用它来替换树上的某一条边,使得我们新得到的树是图的严格次小生成树。

具体地说,我们设当前枚举到的非树边的两个端点分别是 \(u\)\(v\),它们之间的距离是 \(w\),最小生成树的边权总和是 \(sum\)

那么我们的答案就是 \(\min\{sum-w+dist_{u,v}\}\)。其中 \(dist_{u,v}\)\(u\)\(v\) 在最小生成树上最大的边权。

这个可以通过 DFS 预处理得到。

注意在求答案时一定要先判断 \(w\) 是否大于 \(dist_{u,v}\)

我们注意到还有一个问题:如果 \(u\)\(v\) 的最大边权与 \(w\) 相等时怎么办?

其实这个问题也不能难解决:我们只要再预处理一下 \(u\)\(v\) 的次大边权,转移时再判断 \(w\) 是否大于次大边权即可。

记得开 long long。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 503, M = 20003;

int n, m;
struct Node
{
    int u, v, w;
    bool fl; //fl 为 true 表示是树边,否则就是非树边
} a[M];
int tot, head[N], ver[M], nxt[M], edge[M];
int dist[N][N], dist1[N][N]; //两点之间的最大距离和次大距离
int fa[N];
LL res, sum, ans;

inline void add(int u, int v, int w)
{
    ver[++tot] = v, edge[tot] = w, nxt[tot] = head[u], head[u] = tot;
}

int getf(int u)
{
    if (fa[u] == u) return u;
    return fa[u] = getf(fa[u]);
}

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

void dfs(int rt, int u, int f, int maxd, int max2d)
{
    dist[rt][u] = maxd, dist1[rt][u] = max2d;
    for (int i = head[u]; i; i = nxt[i])
    {
        int v = ver[i], w = edge[i];
        if (v == f) continue;
        int maxxd = maxd, maxx2d = max2d;
        if (w > maxxd) maxxd = w; //更新最大值
        else if (w < maxxd && w > maxx2d) maxx2d = w; //一定要注意判断 w 是否小于最大值后再更新次大值
        dfs(rt, v, u, maxxd, maxx2d);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i+=1)
    {
        cin >> a[i].u >> a[i].v >> a[i].w;
    }
    sort(a + 1, a + 1 + m, cmp);
    for (int i = 1; i <= n; i+=1) fa[i] = i;
    for (int i = 1; i <= m; i+=1)
    {
        int u = getf(a[i].u), v = getf(a[i].v);
        if (u != v)
        {
            fa[u] = v;
            sum += a[i].w; 
        	add(a[i].u, a[i].v, a[i].w);
       	 	add(a[i].v, a[i].u, a[i].w); //建立最小生成树
          	a[i].fl = true;
        }
    }
    for (int i = 1; i <= n; i+=1) dfs(i, i, -1, 0, 0);
    ans = 1000000000000000000;
    for (int i = 1; i <= m; i+=1)
        if (!a[i].fl)
        {
            int u = a[i].u, v = a[i].v;
            if (a[i].w > dist[u][v]) //大于最大边权
                ans = min(ans, sum - dist[u][v] + a[i].w);
            else if (a[i].w > dist1[u][v]) //大于次大边权
                ans = min(ans, sum - dist1[u][v] + a[i].w);
        }
    cout << ans << endl;
    return 0;
}
posted @ 2020-03-02 22:23  csxsi  阅读(324)  评论(0编辑  收藏  举报