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

更快更正确的代码见刷题记录(尚未发布)。采用LCA,码量确实大了很多。

一道毒瘤题。upd:毒瘤者人也,非数据也。记一次长达4天的心理战。

题意

次小生成树板子:\(N\)\(M\)分别表示顶点数和边数,\(z\)为边权。给\(M\)条边,求出这张图的次小生成树边权和。

\(1 \leq N \leq 500\), \(1 \leq M \leq 10^4\), \(1 \leq z \leq 10^9\),有重边。

思路

目前找到的次小生成树算法都很简单粗暴,但是思路基本相同:先找出最小生成树\(T\),再把一条非树边\(E\)加到树上,形成环。再在环上拿掉除了\(E\)之外最大的边,形成次小生成树的一项备选方案。在所有备选方案中选出边权和最小的,就是次小生成树。

一个问题:能不能用重边相互替换?
答:可以。

这位dalao的blog讲解了Kruskal和Prim衍生出的次小生成树算法:次小生成树详解及模板(prim 以及 kruskal)
这位dalao姐姐的blog讲了可以用dfs来找树上两点之间的最长边:就是这道题的题解

十分困顿时码的一份代码:

码一份大佬的代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010, M = 10010;
struct Edge{
    int a, b, w;
    bool is_tree;
    bool operator < (const Edge &e) const{
        return w < e.w;
    }
}edge[M];
int n, m;
int p[N], dist1[N][N], dist2[N][N]; // ???????
int h[N], e[M * 2], ne[M * 2], w[M * 2], idx;
int find(int x){
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}
void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int fa, int maxn, int maxv, int dist1[], int dist2[]){
    dist1[u] = maxn, dist2[u] = maxv;
    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if(j != fa){
            if(w[i] > maxn) dfs(j, u, w[i], maxn, dist1, dist2);
            else if(w[i] < maxn && w[i] > maxv) dfs(j, u, maxn, w[i], dist1, dist2);
            else dfs(j, u, maxn, maxv, dist1, dist2);
        }
    }
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        cin >> edge[i].a >> edge[i].b >> edge[i].w;
    }
    sort(edge + 1, edge + 1 + m);
    for(int i = 1; i <= n; i++) p[i] = i;
    LL sum = 0;
    for(int i = 1; i <= m; i++){
        auto& [a, b, w, f] = edge[i];
        int pa = find(a), pb = find(b);
        if(pa != pb){
            p[pa] = pb;
            sum += w;
            f = 1;
            add(a, b, w), add(b, a, w);
        }
    }
    // dfs????????????????
    for(int i = 1; i <= n; i++){
        dfs(i, -1, -1e9, -1e9, dist1[i], dist2[i]);
    }
    LL ans = 1e18;
    for(int i = 1; i <= m; i++){
        auto &[a, b, w, f] = edge[i];
        if(!f){
            if(w > dist1[a][b]) ans = min(ans, sum + w - dist1[a][b]);
            else if(w > dist2[a][b]) ans = min(ans, sum + w - dist2[a][b]);
        }
    }
    cout << ans << '\n';
}
这份代码又简单跑得又快,还AC!orz

upd:留这份代码是因为他用了C++的lambda表达式相关的东西,以及,他为什么要开两个dist数组???

我的经历:

一开始只想到单纯的最小生成树算法,为了避免麻烦,就尝试在求最小生成树的时候处理出两点之间边权最大的边。而且在Prim和Kruskal之间摇摆不定。刚了一晚,未果,气鼓鼓地睡觉去了,顺便骂了说数据太水的人。
第二天骂骂咧咧地打了一半LCA,觉得不合理,骂骂咧咧地关了电脑。
第三天不情不愿地打开代码,终于认识到要把问题放到一棵树上看———毕竟次小生成怎么能没有树的存储方式呢?但是网上的算法实在看起来太慢了!破罐子破摔,打出来再说。于是敲出了下面代码的雏形。但是提交之后一直不给面子地 WA 10pts ,心灰意冷地关了电脑。
第四天麻木地打开代码,逐部分排错,甚至找了一份AC代码对照着看……最后锁定是最小生成树部分出了问题,才灵光一闪:是不是被重边拉垮了?!结果说明是的,

  1. 重边\((u, v, w_1)\)\((u, v, w_2)\)不代表这两条边相等。恰恰相反,在最、次小生成树算法中,这往往意味着你要取给出的\((u, v, w)\)里边权\(w\)最小的边来作为在算法中实际使用的边。
    (来自新编辑的博客

问题解决,开开心心。

当然,在排错时也排出了一些问题,比如数组大小不够。总而言之,得益于这份非常舒适的数据,笔者得以坎坷起伏地AC了这道题。

但如果数据范围再大一点呢?

代码

事实上,由于没有考虑重边互相换的情况,这仍然不是一份完美的代码(应该把重边也存到edge里,如一开始的想法开结构体)。但是留着也算经(错)验(误)的积(收)累(集)啦懒得再码了

AC 45ms

//kruskal
//要选重边里最小的
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 505;
const int M = 1e5 + 5;
const ll INF = 5e11 + 5;
int n, m, cnt, fa[N]; //,lca[N][N]
struct edge {
    int u, v;
    ll w;
} e[M];
vector <int> con[N];
ll lp[N][N], wt, ans; //longest path between i and j
ll mp[N][N];
bool chs[M];//重边;chosen e[i]

void dfs(int x, int lst, int r) {
    int sz = con[x].size();

    for (int i = 0; i < sz; i++) {
        if (con[x][i] != lst) {
            lp[r][con[x][i]] = max(lp[r][x], mp[x][con[x][i]]);
            dfs(con[x][i], x, r);
        }
    }
}

int findf(int x) {
    if (fa[x] == x)
        return x;

    return fa[x] = findf(fa[x]);
}

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

int main() {

    ans = INF;
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= m; i++) {
        int a, b;
        ll c;
        scanf("%d%d%lld", &a, &b, &c);

        if (mp[a][b] == 0 || mp[a][b] > c) {
            cnt++;
            e[cnt].u = a;
            e[cnt].v = b;
            e[cnt].w = c;
            mp[a][b] = c;
            mp[b][a] = c;
            chs[cnt] = false;
        }
    }

    for (int i = 1; i <= n; i++)
        fa[i] = i;

    sort(e + 1, e + cnt + 1, cmp);
    int k = 0;

    for (int i = 1; i <= cnt; i++) {
        if (k == n - 1)
            break;

        int x = findf(e[i].u), y = findf(e[i].v);

        if (x != y) {
            chs[i] = true;
            fa[x] = y;
            wt += e[i].w;
            k++;
            con[e[i].u].push_back(e[i].v);
            con[e[i].v].push_back(e[i].u);
        }
    }

    for (int i = 1; i <= n; i++)
        dfs(i, 0, i);

    for (int i = 1; i <= cnt; i++) {
        if (!chs[i]) {
            if (lp[e[i].u][e[i].v] < e[i].w)
                ans = min(ans, wt - lp[e[i].u][e[i].v] + e[i].w);
        }
    }

    printf("%lld", ans);

    return 0;
}

THE END.

posted @ 2022-05-09 21:08  Searshkiu  阅读(83)  评论(0编辑  收藏  举报