CODE FESTIVAL 2016 Final G Zigzag MST 最小生成樹
阿新 • • 發佈:2018-11-04
題意
- 給你一個圖,對於圖中每一條邊\((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; }