Transportation(最小生成树,虚拟节点)
题意
有\(N\)个岛屿,现在需要将它们联通起来。
可以选择在岛上建立飞机场,花费为\(X_i\);也可以在岛上建立港口,花费为\(Y_i\);还可以在两个岛屿\(A_i\)和\(B_i\)之间修路,花费为\(Z_i\)。
如果两个岛上都有飞机场,则两者可以联通;如果两个岛上都有港口,则两者可以联通;如果两个岛之间有路,则两者可以联通。
两个岛之间也可以通过其他岛间接联通。
求使得所有岛联通的最小花费是多少。
题目链接:https://atcoder.jp/contests/abc270/tasks/abc270_f
数据范围
\(2 \leq N \leq 2 \times 10^5\)
\(1 \leq M \leq 2 \times 10^5\)
\(1 \leq X_i, Y_i, Z_i \leq 10^9\)
思路
如果只能修路的话,问题就可以转换为一个最小生成树问题。那么加上前两种方式应该怎么处理呢?
考虑建立两个虚拟节点,第一个虚拟节点向每个节点(除了两个虚拟节点)连权值为\(X_i\)的边,第二个虚拟节点向每个节点(除了两个虚拟节点)连权值为\(Y_i\)的边。
如果有至少一个岛需要修建飞机场,则第一个虚拟节点就要被联通;如果有至少一个岛需要修建港口,则第二个虚拟节点就要被联通。
因此我们跑4次Kruskal算法:不连通两个虚拟节点,只联通第一个虚拟节点,只联通第二个虚拟节点,两个虚拟节点都联通。然后取4者中的最小值即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 200010, M = 10 * N;
int n, m;
int p[N];
ll X[N], Y[N];
struct Edge
{
int u, v;
ll w;
bool operator < (const Edge &t) const {
return w < t.w;
}
}a[M], b[M], c[M], d[M];
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
ll kruskal(struct Edge a[], int len, int num)
{
for(int i = 1; i <= n + 2; i ++) p[i] = i;
ll res = 0;
int cnt = 0;
sort(a + 1, a + len + 1);
for(int i = 1; i <= len; i ++) {
int u = a[i].u, v = a[i].v;
ll w = a[i].w;
int pu = find(u), pv = find(v);
if(pu != pv) {
res += w;
p[pu] = pv;
cnt ++;
}
}
if(cnt < num - 1) res = 1e18;
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%lld", &X[i]);
for(int i = 1; i <= n; i ++) scanf("%lld", &Y[i]);
for(int i = 1; i <= m; i ++) {
int u, v;
ll w;
scanf("%d%d%lld", &u, &v, &w);
a[i] = {u, v, w}, b[i] = {u, v, w}, c[i] = {u, v, w}, d[i] = {u, v, w};
}
for(int i = 1; i <= n; i ++) {
b[m + i] = {n + 1, i, X[i]};
c[m + i] = {n + 2, i, Y[i]};
d[m + i] = {n + 1, i, X[i]};
}
for(int i = 1; i <= n; i ++) {
d[m + n + i] = {n + 2, i, Y[i]};
}
ll ans = kruskal(a, m, n);
ans = min(ans, kruskal(b, m + n, n + 1));
ans = min(ans, kruskal(c, m + n, n + 1));
ans = min(ans, kruskal(d, m + 2 * n, n + 2));
printf("%lld\n", ans);
return 0;
}