CODE FESTIVAL 2016 Final G Zigzag MST 最小生成树

题意

  • 给你一个图,对于图中每一条边\((a, b, c),\)会以\((b, a + 1, c + 1), (a + 1, b + 1, c + 2), (b + 1, a + 2, c + 3)\)....的方式无限连接,所有的点都是在模\(n\)意义下的,求这个图的最小生成树.

首先一个有关\(Kruskal\)的性质

如果一个联通块内部已经是联通的

那么它内部的状态对最后结果是没有影响的

例如\((1, 2), (2, 3)(3, 4)\)构成的联通块

我们可以把它变成\((1, 2), (1, 3), (1, 4)\)

并且每条边访问过后 两个端点一定在同一个联通块内

那么我们可以把图上无限连的边

全部转到\(0->1->2->...->0\)这个环上去

那么原图转到环上去的边就变成了\((a, a + 1, c + 1),(b, b+1,c + 2)\)

我们对于环上的边显然有这样一个式子

\(val_{a->a+1} = min(val_{a->a+1},val_{a-1->a}+2)\)

那么我们一直更新到环的边权不再改变为止

最后我们用原图的边和还上的边一起做一遍\(Kruskal\)就好了

复杂度\(O(nlogn)\)

Codes

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2e5 + 10;

int n, m, d[N], tmp[N], fa[N];

struct Edge {
    int x, y, z;
    bool operator < (const Edge &T) const {
        return z < T.z;
    }
};

vector<Edge> E;

inline bool changed() {
    for (int i = 0; i < n; ++ i) 
        if (d[i] ^ tmp[i]) return true;
    for (int i = 0; i < n; ++ i) 
        E.push_back((Edge){i, (i + 1) % n, d[i]}), fa[i] = i;
    return false;
}

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

int main() {
    //freopen("gkk.in", "r", stdin);
    //freopen("gkk.out", "w", stdout);

    memset(d, 0x3f, sizeof(d));
    scanf("%d%d", &n, &m);
    for (int x, y, z, i = 1; i <= m; ++ i) {
        scanf("%d%d%d", &x, &y, &z);
        d[x] = min(d[x], z + 1);
        d[y] = min(d[y], z + 2);
        E.push_back((Edge){x, y, z});
    }

    do {
        for (int i = 0; i < n; ++ i)
            tmp[i] = d[i];
        for (int i = 0; i < n; ++ i) 
            d[i] = min(d[i], d[(i + n - 1) % n] + 2);
    }while (changed());

    long long ans = 0;
    sort(E.begin(), E.end());
    for (int j = 0, sz = E.size(); j < sz; ++ j) {
        int u = find(E[j].x), v = find(E[j].y);
        if(u ^ v) ans += E[j].z, fa[u] = v;
    }
    printf("%lld\n", ans);
    
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/brunch/p/9903849.html