3806. 【NOIP2014模拟8.24】小X 的道路修建

19 篇文章 0 订阅
3 篇文章 0 订阅

Description

因为一场不小的地震,Y 省n 个城市之间的道路都损坏掉了,省长希望小X 将城市之间的道路重修一遍。
很多城市之间的地基都被地震破坏导致不能修路了,因此可供修建的道路只有m 条。因为施工队伍有限,省长要求用尽量少的道路将所有的城市连通起来,这样施工量就可以尽量少。不过,省长为了表示自己的公正无私,要求在满足上述条件的情况下,选择一种方案,使得该方案中最贵道路的价格和最便宜道路的价格的差值尽量小,即使这样的方案会使总价提升很多也没关系。
小X 现在手忙脚乱,希望你帮帮他。

Input

第一行包含两个整数n;m。
接下来m 行,每行包含三个整数a; b; c,表示城市a; b 之间可以修建一条价格为c 的无向道路。

Output

若存在合法方案,则第一行包含一个整数,表示最贵道路的价格和最便宜道路的价格的最小差值;
否则第一行包含一个整数−1。

Sample Input

输入1:
5 10
1 2 9384
1 3 887
1 4 2778
1 5 6916
2 3 7794
2 4 8336
2 5 5387
3 4 493
3 5 6650
4 5 1422
输入2:
2 0

Sample Output

输出1:
1686
输出2:
-1

Data Constraint

• 对于30% 的数据,n;m ≤ 20。
• 对于60% 的数据,n ≤ 1000,m ≤ 4000。
• 对于100% 的数据,2 ≤ n ≤ 2000,0 ≤ m ≤ 15000,a ̸= b,1 ≤ c ≤ 2 × 10^9。

Solution

对于60pts,枚举最小边,从这里开始做最小生成树,由于最小生成树最大边最小,可以保证是最优答案。

对于100pts,我们发现我们每次都做了一遍最小生成树,其实没有必要,我们从大到小枚举最小的边后,我们需要做的世纪上是在没加入一条边后,维护出当前图的最小生成树,那么当加入一条边时,我们考虑它是否与之前的边构成环,如果构成环,就将环上最大边删去,用当前这条较小边代替一定更优,如果不构成环,就直接加入边中。找环中最大边可以用 DFS 实现。 若图中现有的边数为 n − 1,我们就可以更新答案。 时间复杂度 O(m log m + mn),期望得分 100 分。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define I int
#define F(i,a,b) for(I i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof(a))
#define N 15010
using namespace std;
void rd(I &x){
	x=0;I w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	x*=w;
}
I n,m,cnt,tot,f[2010],st[2010],fa[2010],E[2010],ls[N],nx[N*2],t[N*2],id[N*2],ans=2147483647;
struct node{I x,y,z;}a[N];
I cmp(node x,node y){return x.z>y.z;}
I get(I x){return f[x]==x?x:f[x]=get(f[x]);}
void add(I x,I y,I z){t[++tot]=y,nx[tot]=ls[x],ls[x]=tot;id[tot]=z;}
void dg(I x,I y,I op){
	fa[x]=y;st[x]=op;
	for(I k=ls[x];k;k=nx[k]) if(t[k]!=y){dg(t[k],x,id[k]);}
}
I work(I l,I r){
	cnt=0;
	F(i,1,n) f[i]=i;
	F(i,l,r){
		I fx=get(a[i].x),fy=get(a[i].y);
		if(fx!=fy) {f[fx]=fy,cnt++;}
		if(cnt==n-1) return 1;
	}
	return 0;
}
I main(){
	freopen("construction.in","r",stdin);
	freopen("construction.out","w",stdout);
	rd(n),rd(m);
	F(i,1,m){rd(a[i].x),rd(a[i].y),rd(a[i].z);}
	sort(a+1,a+1+m,cmp);
	if(!work(1,m)){
		printf("-1\n");	
		return 0;
	}
	cnt=0;
	F(i,1,n) f[i]=i;
	F(i,1,m){
		I fx=get(a[i].x),fy=get(a[i].y);
		if(fx!=fy){
			f[fx]=fy,cnt++;
			E[++E[0]]=i;
		}
		else{
			tot=0;
			mem(ls,0);
			mem(fa,0);
			F(j,1,E[0]){
				add(a[E[j]].x,a[E[j]].y,j);
				add(a[E[j]].y,a[E[j]].x,j);
			}
			dg(a[i].x,0,0);
			I x=a[i].y,ma=0,mk=0;
			while(x!=a[i].x){
				if(a[E[st[x]]].z>ma){
					ma=a[E[st[x]]].z;
					mk=st[x];
				}
				x=fa[x];
			}
			E[mk]=i;
		}
		if(cnt==n-1){
			I ma=0;
			F(j,1,E[0]) ma=max(ma,a[E[j]].z);
			ans=min(ans,ma-a[i].z);
		}
	}
	printf("%d\n",ans);
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值