P2330 [SCOI2005] 繁忙的都市——最小生成树(Kruskal算法)
题目出处:P2330
理解题目
题目说城市中有n个交叉路口,这些路口通过双向道路连接,所有路口都是连通的。每条道路有一个分值,分值越小表示越需要改造。市长希望改造的道路满足三个条件:首先必须连通所有路口,其次道路数量尽可能少,然后在满足前两个条件下,这些道路中的最大分值尽可能小。
浅谈Kruskal 算法
我们需要把所有的路口连在一起,在最开始的时候我天真的认为只需要计算每个端点的度便加上一点点贪心可以了,结果是我肤浅...白白浪费了差不多一个小时(嚎啕大哭)。但是我后来想明白了,一个点(路口)可能会和多个路口相连,这不是树吗?边权最小且全部联通这就是最小生成树的定义都怪数据太水(差评),所以我就想到了Kruskal 算法(当当!)。
该算法的思路就是 找父亲 ,先按权值从小到大排序(贪心),并每个点(路口)当成一个集合。
bool cmp(const Edge& a, const Edge& b) {
return a.c < b.c;
}
/*我的另一种写法:
bool cmp(edge a,edge b)d{
if(a.w>b.w)
return a.c<b.c;
return a.c>b.c;
}*/
以样例为例:
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
在排序后就变成了
1 2 3
1 4 5
2 3 6
2 4 7
3 4 8
从最小的边开始,我们可以把路口1和路口2结合,任意找一个当“父亲”,然后遍历接下来的每一个点,到了最后就成为了一个树,还是最小生成树!但是后面在结合时找谁当父亲?怎么结合都需要考虑。
//并查集:管理路口连通性
class UnionFind {
private:
vector<int> parent;// 父节点数组
public: // 构造函数:初始化n个独立集合(每个路口初始独立)
UnionFind(int n) {
parent.resize(n + 1);
for (int i = 1; i <= n; ++i) {
parent[i] = i; // 初始父节点指向自己
}
}
// 查找
int find(int x) {
if (parent[x] != x) { // 如果当前节点不是根
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并操作
void unite(int x, int y) {
int fx = find(x), fy = find(y);// 找到两个根节点
if (fx != fy) {
parent[fx] = fy;
}
}
};
综上:此题得解!
解法思考
所以需要找到最小生成树,然后输出边的数量(n-1)和其中最大的边权。那这样应该就能满足所有条件了。(事实证明就是如此)
所以说我们应该想到用并查集来完成本人也是很聪明的拷贝了模板,我们可以使用看到最小生成树pdf中的Kruskal 算法,它恰好按边权升序处理,优先选小权值边,天然满足"最大分值最小"需求。(非常的完美)。
代码
#include <bits/stdc++.h>
using namespace std;
// 道路结构体:表示两个交叉路口之间的连接道路
struct Edge {
int u, v, c;// u:起点路口编号,v:终点路口编号,c:道路分值
Edge(int u, int v, int c) : u(u), v(v), c(c) {}
};
// 比较函数:用于道路按分值升序排序(核心的贪心策略
bool cmp(const Edge& a, const Edge& b) {
return a.c < b.c;//分值越小的道路优先级越高
/*我的另一种写法:
bool cmp(edge a,edge b)d{//比较函数,按权重排序
if(a.w>b.w)
return a.c<b.c;
return a.c>b.c;
}*/
}
//并查集:管理路口连通性
class UnionFind {
private:
vector<int> parent;// 父节点数组
public: // 构造函数:初始化n个独立集合(每个路口初始独立)
UnionFind(int n) {
parent.resize(n + 1);
for (int i = 1; i <= n; ++i) {
parent[i] = i; // 初始父节点指向自己
}
}
// 查找
int find(int x) {
if (parent[x] != x) { // 如果当前节点不是根
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并操作
void unite(int x, int y) {
int fx = find(x), fy = find(y);// 找到两个根节点
if (fx != fy) {
parent[fx] = fy;
}
}
};
int main() {
int n, m;
cin >> n >> m;
// 特判:当只有一个路口时无需任何道路
if (n == 1) {
cout << "0 0" << endl;
return 0;
}
vector<Edge> edges;// 存储所有道路
for (int i = 0; i < m; ++i) {
int u, v, c;
cin >> u >> v >> c;
edges.emplace_back(u, v, c);
}
// 按道路分值升序排序(贪心选择最小边)
sort(edges.begin(), edges.end(), cmp);
UnionFind uf(n);// 初始化
int max_c = 0;
int selected = 0;
//遍历排序后的道路
for (const auto& e : edges) {
int fu = uf.find(e.u);
int fv = uf.find(e.v);
if (fu != fv) {// 如果不在同一集合
uf.unite(fu, fv);
max_c = max(max_c, e.c);// 更新max
if (++selected == n - 1) break;// 已选n-1条边时提前终止
}
}
cout << n - 1 << " " << max_c << endl;
return 0;
}
谢谢观看!
本文作者:ccgc718
本文链接:https://www.cnblogs.com/ccgc718/p/18715287
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步