原题
题目描述
有 N N N 个沿海城市,张三需要规划所有城市的交通,可以规划三种交通方式:
对于每个城市
1
≤
i
≤
N
1\leq i\leq N
1≤i≤N,可以花
X
i
X_i
Xi 元建一个机场
对于每个城市
1
≤
i
≤
N
1\leq i\leq N
1≤i≤N,可以花
Y
i
Y_i
Yi 元建一个港口
有
M
M
M 条待建设公路,对于
1
≤
i
≤
M
1\leq i\leq M
1≤i≤M,可以花
Z
i
Z_i
Zi 元在城市
A
i
A_i
Ai 和
B
i
B_i
Bi 之间建一条双向公路
如果两个城市
u
,
v
u,v
u,v 满足下列条件之一,则
u
,
v
u,v
u,v 可以互相到达:
- u , v u,v u,v 都有机场
- u , v u,v u,v 都有港口
-
u
u
u 和
v
v
v 之间有公路
问至少花多少代价才能让任意两个城市互相连通(可以通过中转城市)
输入格式
N
M
N~M
N M
X
1
X
2
⋯
X
N
X_1 X_2 \cdots X_N
X1X2⋯XN
Y
1
Y
2
⋯
Y
N
Y_1 Y_2 \cdots Y_N
Y1Y2⋯YN
A
1
B
1
Z
1
A_1 B_1 Z_1
A1B1Z1
A
2
B
2
Z
2
A_2 B_2 Z_2
A2B2Z2
…
\dots
…
A
M
B
M
Z
M
A_M B_M Z_M
AMBMZM
输出格式
输出最少的花费
样例
输入样例#1
4 2
1 20 4 7
20 2 20 3
1 3 5
1 4 6
输出样例#1
16
输入样例#2
3 1
1 1 1
10 10 10
1 2 100
输出样例#2
3
输入样例#3
7 8
35 29 36 88 58 15 25
99 7 49 61 67 4 57
2 3 3
2 5 36
2 6 89
1 6 24
5 7 55
1 3 71
3 4 94
5 6 21
输出样例#3
160
数据范围与提示
1
≤
A
i
<
B
i
≤
N
,
1
≤
M
1\leq A_i < B_i \leq N,1\leq M
1≤Ai<Bi≤N,1≤M
N
,
M
≤
2
×
1
0
5
N,M\leq 2\times 10^5
N,M≤2×105
1
≤
X
i
,
Y
i
,
Z
i
≤
1
0
9
1\leq X_i,Y_i,Z_i\leq 10^9
1≤Xi,Yi,Zi≤109
保证不存在重边
解法
由题意可知:要求选出图上权值和最小的边集
V
V
V,使得所有点两两联通
这让我们想起了什么?
最小生成树
这里有三种情况:
轮船,飞机,公路
其中两个城市通过轮船和飞机的方式相似,都是要选择点
两个城市公路的方式是要选择边
边点各有方法统计
于是暴力的方案一、二诞生了
方案一
暴力选择边(公路),选完后缩点,暴力跑点(轮船和飞机)
不过缩点很麻烦,可以优化
方案二
就是方案中缩点升级为并查集
但是这两种方法实在是麻烦,速度又慢,数据是 N , M ≤ 2 × 1 0 5 N,M\leq 2\times 10^5 N,M≤2×105,理论最快速度 O ( n 2 ) O(n^2) O(n2),绝对超时
不难发现,问题就出在:港口、机场、公路的分配上
如果我们能将三者统一,都视作选择点或选择边,那么题目的难度就大大降低了
由题意可知:要求选出图上权值和最小的边集 V V V,使得所有点两两联通
这让我们想起了什么?
最小生成树
1.选择边
最小生成树就是专门处理选择边的
由于是稀疏图,kruskal足以应对,以下讲解的方法都是kruskal
方案三
我们可以思索一下:飞机要走天路,轮船要走水路
不妨把天空视作一个城市,海洋也视作一个城市
因此,要使一架飞机能够从天空中到达城市 a a a或从城市 a a a去往天空中,代价是 X i X_i Xi
于是我们可以将天空视作一个超级节点 n + 1 n+1 n+1,每个节点都有一条与 n + 1 n+1 n+1的边,边权是 X i X_i Xi
轮船也是一样
但如果直接加进这两个点跑最小生成树的话,仍然有问题
举个例子
3 1 1 1 1 10 10 10 1 2 100
答案:
3
正确的走法是这样的
可直接跑可能会跑出这样的结果
也就意味着,两个超级节点并不一定在最小生成树中
所以,要讨论两个超级节点是否在最小生成树中,共四种情况
即便是两个超级节点都在最小生成树里的情况依然会出现“废边(多余的边)”
但在另三种中一定存在没有“废边”的情况
用kruskal跑四遍即可
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 7, md = 1e9 + 7;
int n, m, x[N], y[N];
struct edge {
int u, v, w;
bool operator<(const edge b)
{
return w < b.w;
}
} e[N << 2], e1[N << 2];
int cnt;
void addE(int u, int v, int w)
{
e[++cnt] = { u, v, w };
}
int fa[N<<1];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
int kruskal()
{
sort(e + 1, e + cnt + 1);
for (int i = 1; i <= n + 2; i++)
fa[i] = i;
int sum = 0;
for (int i = 1; i <= cnt; i++) {
int u = e[i].u, v = e[i].v, w = e[i].w;
u = find(u), v = find(v);
if (u == v)
continue;
if (u < v)
swap(u, v);
fa[u] = v;
// cout << " " << w << endl;
sum += w;
}
// cout << sum << endl;
for (int i = 1; i <= n; i++)
if (find(i) != 1)
return 1e18;
return sum;
}
signed main()
{
scanf("%lld%lld", &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, a, b, z; i <= m; i++)
scanf("%lld%lld%lld", &a, &b, &z),
addE(a, b, z), e1[i] = e[i];
int ans = kruskal();//情况1:两个超级节点都不在最小生成树里
for (int i = 1; i <= n; i++)
addE(n + 1, i, x[i]);
ans = min(ans, kruskal());//情况2:超级节点1(飞机)在最小生成树里,超级节点2(轮船)不在最小生成树里
for (int i = 1; i <= m; i++)
e[i] = e1[i];
cnt = m;
for (int i = 1; i <= n; i++)
addE(n + 2, i, y[i]);
ans = min(ans, kruskal());//情况3:超级节点1(飞机)不在最小生成树里,超级节点2(轮船)在最小生成树里
for (int i = 1; i <= n; i++)
addE(n + 1, i, x[i]);
ans = min(ans, kruskal());//情况1:两个超级节点都在最小生成树里
printf("%lld\n", ans);
}
2.选择点
边化作点与点化作边不同,边化做点的要求十分苛刻:每一条边中,都是点 A A A与其他点相连,但数据很明显不是,因此无法直接实现