版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Zee_Chao/article/details/87828482
本人初学,水平有限,若有不足,恳请赐教!
这道题有很大的迷惑性!如果直观地从字面上理解,题目的意思是说由一个图可以得到的所有生成树中,每个生成树中都能由条件计算得到一个,那么所有得到的中最小的是几(即最优树结构的流水线耗时)?
通过观察并结合条件可以发现,每个实际上就等于对应的生成树中最大权值边的权值。
实际上,这道题等价于求一个图的最小生成树(MST)中的最大边的权值(注意,MST只是满足题目要求的最优树结构中的其中一个)!可以使用反证法来证明。按照这个思路,也就是说题目的求解与根结点的编号无关,也不用理会题目给出的计算规则,只要套用MST的模板就可以了!
由于题目与图的边有很大关联,故考虑根据Kruskal算法(俗称“加边法”)解决该问题。
对于Kruskal算法,一个关键的问题就是在决定纳入一个边之前需要判断此边的纳入是否会使生成树形成回路。经典的解决方法就是使用并查集。如果新纳入的边在查找两端点在集合中的根结点后发现它们根结点相同,则说明此边的纳入将导致回路,此边需要舍去。否则将其纳入并将两结点所属集合合并。直至生成树中的边数恰好比结点数少1时终止算法。在从中找出权值最大的边输出即可。
可以利用C++的STL中提供的优先级队列对此算法进行优化。首先,定义一个容纳边的优先级队列并规定权值小的边优先级更高。这样在读入数据时就可以直接对数据进行排序了。然后,基于此条件,只要保存并输出最后一个纳入MST的边的权值就可以了。
具体代码如下:
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
int p[500010]; //全局数组作为并查集
typedef struct Edge //定义边类型
{
int u, v, w;
}Edge;
struct cmp //自定义优先级队列的比较函数
{
bool operator()(const Edge &a, const Edge &b)
{
return a.w > b.w; //边权值小的优先级高
}
};
int find(int x) //并查集查找根节点
{
return p[x] == x ? x : p[x] = find(p[x]);
}
int main()
{
int tmax = 0; //输出结果
int n, m, root; //root只读入数据,后续程序与其无关
cin >> n >> m >> root;
for(int i = 0; i < n + 1; i++) //初始化并查集,注意题目结点顺序是从1开始而不是0
p[i] = i;
priority_queue<Edge, vector<Edge>, cmp> pq_e;
for(int i = 0; i < m; i++) //读入边的数据并存储到优先级队列中
{
Edge e;
int u, v, w;
cin >> u >> v >> w;
e.u = u, e.v = v, e.w = w;
pq_e.push(e);
}
for(int i = n - 1; i > 0;) //由于MST的边数必定比结点数少1,故由此控制循环
{
Edge e = pq_e.top(); //队列头部元素一定是所有未访问边中权值最小的
pq_e.pop();
int u = e.u;
int v = e.v;
int ru = find(u);
int rv = find(v);
//在并查集中查找此边的两端点编号是否有共同的根,若有则说明若将此边纳入会出现回路,故应舍去
if(ru == rv) continue;
//没有则说明此边可以纳入MST中,由于题目的要求,最终的结果一定是最后纳入MST中边的权值
//又因为优先级队列中的元素已按照最小权值优先的方式排列好了,因此可以不断进行覆盖操作
//另外不要忘记将两端点所属集合合并到一起!
else
{
p[ru] = rv; //合并集合
tmax = e.w; //不断覆盖直至退出循环
i--;
}
}
cout << tmax;
return 0;
}