1. 程式人生 > >CODE FESTIVAL 2016 Final G Zigzag MST 最小生成樹

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;
}