这是一道运用最小生成树(MST)算法(具体是Kruskal算法)来解决的问题,题目场景大概是在给定一些点的坐标情况下,要连接这些点形成一定数量的连通块,求满足条件的最大边长(即最小生成树中最大的边权重)。以下是详细的题解,包括对代码的逐段分析以及时间复杂度和空间复杂度分析:
题目分析
从代码的逻辑来看,题目应该是这样的:给定(n)个点的坐标,要将这些点连接起来,使得最终形成(k)个连通块。连接两个点的边的权重是这两个点之间的欧几里得距离。需要求出在满足形成(k)个连通块的情况下,连接点的边中最大的边长(即边的权重)是多少。
代码逐段分析
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 510, M = N * N / 2;
int n, k, m;
struct Edge
{
int a, b;
double w;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}e[M];
PII q[M];
int p[N];
- 引入必要的头文件:
cstring
用于字符串操作(这里未用到)。iostream
用于输入输出操作。algorithm
用于排序等算法操作。cmath
用于数学运算,特别是计算平方根。
- 使用
#define
宏定义了x
和y
分别表示pair
类型的第一个和第二个元素,方便后续使用。 - 定义了命名空间
std
,使得可以直接使用标准库中的函数和类型。 - 定义了
PII
作为pair<int, int>
的别名,用于表示点的坐标。 - 定义了常量
N
表示点的最大数量,M
表示边的最大数量((N * N / 2) 是完全图的边数上限)。 - 声明了变量
n
表示点的数量,k
表示最终要形成的连通块数量,m
用于记录边的数量。 - 定义了结构体
Edge
来表示边,包含两个端点a
和b
以及边的权重w
,并重载了小于运算符<
,以便对边按权重进行排序。 - 定义了数组
e[M]
用于存储所有的边,q[M]
用于存储点的坐标,p[N]
用于实现并查集,记录每个点的父节点。
double get_dist(PII a, PII b)
{
int dx = a.x - b.x;
int dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
get_dist
函数用于计算两个点a
和b
之间的欧几里得距离。先计算横坐标之差dx
和纵坐标之差dy
,然后根据勾股定理计算两点间的距离并返回。
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
find
函数实现了并查集的查找操作,用于找到节点x
的根节点。如果当前节点的父节点不是自身,就递归地查找其父节点,并进行路径压缩(将x
直接指向根节点),最后返回根节点。
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
e[m ++ ] = {i, j, get_dist(q[i], q[j])};
sort(e, e + m);
for (int i = 0; i < n; i ++ ) p[i] = i;
int cnt = n;
double res = 0;
for (int i = 0; i < m; i ++ )
{
if (cnt <= k) break;
int a = find(e[i].a), b = find(e[i].b);
double w = e[i].w;
if (a != b)
{
p[a] = b;
cnt -- ;
res = w;
}
}
printf("%.2lf\n", res);
return 0;
}
- 在
main
函数中:- 首先读取点的数量
n
和最终要形成的连通块数量k
。 - 然后通过循环读取每个点的坐标,并存储在数组
q
中。 - 接下来通过两层嵌套循环,计算每两个点之间的边的信息(端点和权重),并存储在数组
e
中,同时记录边的数量m
。 - 对所有的边按照权重进行排序。
- 初始化并查集,将每个点的父节点设为自身。
- 定义变量
cnt
记录当前的连通块数量(初始为n
,每个点都是一个单独的连通块),res
用于存储最终的结果(最大边长)。 - 遍历排序后的边:
- 如果当前的连通块数量
cnt
已经小于等于k
,则停止遍历。 - 查找当前边的两个端点在并查集中的根节点
a
和b
,获取边的权重w
。 - 如果两个根节点不同,说明这条边可以将两个不同的连通块合并,将
a
的父节点设为b
,连通块数量cnt
减 1,并更新res
为当前边的权重。
- 如果当前的连通块数量
- 最后按照要求的格式输出结果
res
,程序结束。
- 首先读取点的数量
时间复杂度分析
- 读取点的坐标的时间复杂度为 (O(n))。
- 计算边的信息的时间复杂度为 (O(n^2))(两层嵌套循环)。
- 对边进行排序的时间复杂度为 (O(m \log m)),其中 (m = \frac{n(n - 1)}{2}),近似为 (O(n^2 \log n))。
- 遍历边并进行并查集操作的时间复杂度为 (O(m \alpha(n))),其中 (\alpha(n)) 是阿克曼函数的反函数,在实际应用中几乎为常数,所以这部分时间复杂度近似为 (O(m)),即 (O(n^2))。
综合来看,总的时间复杂度为 (O(n^2 \log n))。
空间复杂度分析
- 存储点坐标的数组
q[M]
,空间复杂度为 (O(n))(因为最多 (n) 个点)。 - 存储边信息的数组
e[M]
,空间复杂度为 (O(n^2))(边的数量最多为 (\frac{n(n - 1)}{2}))。 - 并查集数组
p[N]
,空间复杂度为 (O(n))。
因此,总的空间复杂度为 (O(n^2))。