UVA上的题就是让人眼前一亮,不同于那些赤裸裸的生成树水题,该题稍加了变化,不是求最小生成树,而是求最苗条生成树 。
因为生成树有很多,而且每一棵生成树的最大边与最小边只差也是不确定的 。所以只能枚举所有的生成树 。
套用最小生成树模板 ,我们可以枚举生成树的起点位置,然后向后推终点位置,当n个点全部连通时,那么这棵生成树的边集就是[L,R] 。因为边事先都排好序了, 那么该树的苗条值就是e[R] - e[L] 。
这样从小到大枚举所有的L ,不断更新答案,就可以了 。
忍不住再说一下并查集,这是一个很厉害的数据结构, 其效率非常高,在平摊意义下,find函数的时间复杂度几乎可以看成是常数, 而union显然也是常数时间 。
其精彩之处在于那个很短的路径压缩(find函数), 《挑战》上还用了一个rank函数来进一步优化,不过对于时间紧张的比赛来说,完全可以省略,只写少量的代码即可。
细节参见代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 5;
const int INF = 1000000000;
const int maxm = maxn * (maxn - 1) /2;
int n,m,p[maxn];
struct node{
int a,b,v;
}e[maxm];
bool cmp(node a,node b) {
return a.v < b.v;
}
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
int solve() {
int ans = INF;
for(int L=0;L<m;L++) {
for(int i=1;i<=n;i++) p[i] = i; //初始化并查集
int cur = 1;
for(int R=L;R<m;R++) {
int x = find(e[R].a) , y = find(e[R].b);
if(x != y) { p[x] = y; cur++; }
if(cur == n) {
ans = min(ans,e[R].v - e[L].v); break;
}
}
if(cur != n) break; //如果该起点L不符合要求,那么接下来的也不符合 。
}
return ans;
}
int main() {
while(~scanf("%d%d",&n,&m)) {
if( !n && !m ) return 0;
for(int i=0;i<m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].v);
sort(e,e+m,cmp);
int ans = solve();
if(ans == INF) printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}