我的思考——修复公路
Euler_Pursuer · · 题解
我用的方法是并查集和生成最小树。
我打算详细地讲一讲。
并查集
并查集是对树的一种操作,旨在找到某个节点的公共祖先(最老公共祖先)。我们先讲一下并。
并
并就是讲两个节点合并到一个集合里面(这个集合必须是树),每个节点对应一个祖先,最老公共祖先的祖先就是自己,而每个节点在合并前的初始值也是自己,也就是:若有一个节点
void together(int *b, int a, int *s)//为了映照上面的说明,a和b就这样弄吧
{
if(s[*b]==*b)//a没有祖先时
s[*b]=a;//令a为b的父亲
else
together(&s[*b], a, s);//否则继续找b的祖先,直到b的最老祖先,然后将b的最老祖先的父亲设为a
}
查
有时候我们需要查某个节点的最老祖先是谁,主要是为了下面判断某两个节点的最老祖先是否相同而准备的。假如有这样一组关系:
比如我们要找
int findfather(int s[], int x)
{
if(x!=s[x])//如果不是最老祖先,那么就往上继续找
x=findfather(s, s[x]);//从上面传过来的参数
return s[x];//是祖先,就返回最老祖先的值
}
但是,这样做的话,会有很多重复工作。比如我刚才找
int findfather(int *s, int x)//*s代表的是直接对s进行操作
{
if(x!=s[x])//不是最老祖先
s[x]=findfather(s, s[x]);//改变其指向,一直往上指,直到最老祖先。
return s[x];//是最老祖先,返回最老祖先的值
}
这种方法十分巧妙,压缩路径之后,查找的速度也会快得多。
关于本题目
由于题目原本不是树,而是图,而题目又问的是最短修好路得时间,如果是图,那么会有多条路联通两个节点,而其中必定有一条最短时间修好的路,那么最终必定是其中的包含的最小树,所以我们要生成最小树。
最小生成树之Kruskal算法
这个算法用到的方法是,先将所有边按照权重(此处是时间长短)排序,我是从小到大排序的。排序用到的算法复杂度只能是
并的操作,这样还是树。直到最后,这个算法完毕后,最小树就生成了。最小生成树具体算法如下:
int TREE(E *e, int *s)//直接对图和祖先值进行操作
{
int i, total=0;
SORT(e, 1, M);//对边进行权重大小排序
for(i=1;i<=M;i++)
if(findfather(s, s[e[i].a])!=findfather(s, s[e[i].b]))//是否有相同最老祖先
{
together(&s[e[i].a], s[e[i].b], s);//并操作
total++;//计算节点,为后面查看是否每个村庄都能连通做准备
ans=e[i].t;//我将ans定义为了全局变量,表示最小树中的最大通路时间
}
return total;
}
最后实现
#include <iostream>
using namespace std;
int N, M, ans;//全局变量,函数也可以直接用,无需传参数
struct E//图的边
{
int a;
int b;//俩顶点
int t;//修复时间
};
//中间忽略一堆函数,上面写了这里就不写了
int main()
{
int i, j, a, b, c;
int s[100010];
E e[100010];
cin>>N>>M;
for(i=1;i<=N;i++)
s[i]=i;
for(i=1;i<=M;i++)
cin>>e[i].a>>e[i].b>>e[i].t;
c=TREE(e, s);//生成最小树,并返回枝干数
if(c!=N-1)//如果没有将所有村庄连接起来(N个节点的树的枝干数目必须是N-1)
ans=-1;//则答案为不可能
cout<<ans;//否则输出生成最小树时处理好的答案
return 0;
}