题目地址:
https://www.acwing.com/problem/content/1146/
有一个 m m m行 n n n列的点阵,相邻两点可以相连。一条纵向的连线花费一个单位,一条横向的连线花费两个单位。某些点之间已经有连线了,试问至少还需要花费多少个单位才能使所有的点全部连通。
输入格式:
第一行输入两个正整数
m
m
m和
n
n
n。以下若干行每行四个正整数
x
1
,
y
1
,
x
2
,
y
2
x_1,y_1,x_2,y_2
x1,y1,x2,y2,表示第
x
1
x_1
x1行第
y
1
y_1
y1列的点和第
x
2
x_2
x2行第
y
2
y_2
y2列的点已经有连线。输入保证
∣
x
1
−
x
2
∣
+
∣
y
1
−
y
2
∣
=
1
|x_1−x_2|+|y_1−y_2|=1
∣x1−x2∣+∣y1−y2∣=1。
输出格式:
输出使得连通所有点还需要的最小花费。
数据范围:
1
≤
m
,
n
≤
1000
1≤m,n≤1000
1≤m,n≤1000
0
≤
c
≤
10000
0≤c≤10000
0≤c≤10000,
c
c
c为已经存在的连线数
其实就是规定有些边必选的情况下的最小生成树问题,可以用Kruskal算法,参考https://blog.csdn.net/qq_46105170/article/details/116115619。但是这题的边长只有两种,且是有规律的,所以可以直接先存边长为 1 1 1的边,再存边长为 2 2 2的边,这样可以省掉排序的时间。代码如下:
#include <iostream>
using namespace std;
const int N = 1010, M = N * N, K = 2 * N * N;
int n, m, cnt;
int ids[N][N];
struct Edge {
int a, b, w;
} e[K];
int p[M];
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
void get_edges() {
// 下标0和2是上下的边,下标1和3是左右的边
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}, w[4] = {1, 2, 1, 2};
for (int r = 0; r < 2; r++)
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
for (int d = 0; d < 4; d++)
// 先添加边长为1的边(上下),后添加边长为2的边(左右)
if (d % 2 == r) {
int x = i + dx[d], y = j + dy[d];
if (1 <= x && x <= m && 1 <= y && y <= n) {
int a = ids[i][j], b = ids[x][y];
if (a < b) e[cnt++] = {a, b, w[d]};
}
}
}
int main() {
cin >> m >> n;
for (int i = 1, t = 1; i <= m; i++)
for (int j = 1; j <= n; j++, t++)
ids[i][j] = t;
for (int i = 1; i <= m * n; i++) p[i] = i;
int x1, y1, x2, y2;
while (cin >> x1 >> y1 >> x2 >> y2) {
int a = ids[x1][y1], b = ids[x2][y2];
p[find(a)] = find(b);
}
get_edges();
int res = 0;
for (int i = 0; i < cnt; i++) {
int pa = find(e[i].a), pb = find(e[i].b);
if (pa != pb) {
res += e[i].w;
p[pa] = p[pb];
}
}
cout << res << endl;
return 0;
}
时空复杂度 O ( m n ) O(mn) O(mn)。