题意
- 给你一个图,对于图中每一条边\((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;
}