Codeforces 125E MST Company (单度限制最小生成树)


E. MST Company

time limit pertest 8 seconds

memory limit pertest 256 megabytes

input standardinput

output standardoutput

The MST(Meaningless State Team) company won another tender for an important statereform in Berland.

There are n cities inBerland, some pairs of the cities are connected by roads. Each road has itsprice. One can move along any road in any direction. The MST team should carryout the repair works on some set of roads such that one can get from any cityto any other one moving only along the repaired roads. Moreover, this setshould contain exactly k capitalroads (that is, the roads that start or finish in the capital). The number ofthe capital is 1.

As the budgethas already been approved, the MST Company will profit by finding the set withminimum lengths of roads.

Input

The first inputline contains three integers n, m, k (1 ≤ n ≤ 5000;0 ≤ m ≤ 105;0 ≤ k < 5000), where n is thenumber of cities in the country, m is thenumber of roads in the country, k is thenumber of capital roads in the required set. Then m linesenumerate the roads in question. Each road is specified by three numbers ai, bi, wi (1 ≤ ai, bi ≤ n1 ≤ w ≤ 105), where ai, bi are thenumbers of cities linked by a road and wi is itslength.

Between eachpair of cities no more than one road exists. There are no roads that start andfinish in one city. The capital's number is 1.

Output

In the firstline print the number of roads in the required set. The second line shouldcontain the numbers of roads included in the sought set. If the sought set doesnot exist, print -1.

Examples

input

4 5 2
1 2 1
2 3 1
3 4 1
1 3 3
1 4 2

output

3
1 5 2 


一看题目有MST,我以为是一道简单的最小生成树的题目。但题目的难度超过了我的想象。

这道题目确实是和最小生成树有关系,但是其他条件限制。
题目的大概意思是说要把一个国家的所有城市连起来(就是最小生成树),但是与首都相连的道路必须有K条(首都是1),在这两个条件下求最小花费。
输入N城市个数,M道路条数,K首都的度数。


那么这道题目就是限制1的度数的最小生成树,固定了顶点的度数。


拿到题目我开始打算跑两边Kruskal算法,第一遍处理与1相连的边,第二遍处理其他边
这样跑出来的最小生成树必然是满足条件的
写了以后WA以后才发现算法的大BUG,这样可以构建出符合条件的生成树,但也可能构建不出来。第一遍处理用Kruskal处理与1相连的边的时候,会影响后面边的选取。那么会造成可能前面的代价最小,但后面没有边可选的窘境。
这时候正确的解应该是前面付出点代价,选花费稍大的边,保证后面有边可选。能构成一颗生成树。毕竟优先保证有解。


现在我们换一种思路,假设我们排序以后,得到一颗生成树,并且1的度数恰好是K
此时我们就直接得到答案。这是最理想的情况。这肯定不是所有的数据都可以实现。
如果不能一次得到答案,拿我们调整一下Kruskal算法中的候选边的顺序。
给边加一个权重,来调整与1相连的边在候选边中的位置。继续跑Kruskal算法。
然后二分权重,循环下去。这其中可能会得到答案。
如果这时候还没有得到答案,但此时已经很接近答案了,我们可以在二分一次。
这时候换一种策略,选满了K条以后就不在把与1相连的边加入。
这样操作下来就满足条件,代码纠结了好久。

详情请看代码


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<functional>
#include<algorithm>
#define maxn 50005
#define maxm 100005
using namespace std;
int ance[maxn],ans[maxn];
int x[maxm],y[maxm],w[maxm];
int sig[maxm];
int cnt,captedge;
int n,m,k;
int deg;
double l,r,mid;
bool cmp(int a,int b)//加权以后排序
{
 return (w[a]+(x[a]==1)*mid)<(w[b]+(x[b]==1)*mid);//这个算法的核心
}//用Mid调整顺序,最后会得到 权重相同的情况与1相连的边在前面 x【a】相同的情况下权重小的在前面
//如果不是很理解,建议把下面注释去掉,看排序情况
int findance(int x)//并查集操作
{
	if(ance[x]==x)
		return x;
	else
		return ance[x]=findance(ance[x]);
}
void Kruskal(bool flag)
{
	cnt=captedge=0;
	for(int i=1;i<=n;i++)
		ance[i]=i;//并查集初始化
	sort(sig,sig+m,cmp);//排序
	for(int i=0;i<m;i++)
	{
		if(cnt+1==n)
			return ;
		int j=sig[i];
		int u=findance(x[j]);//并查集合并准备操作
		int v=findance(y[j]);
		if((u!=v)&&(flag||(captedge+(x[j]==1))<=k))//两种不同的操作策略
		//flag=1 普通的Kruskal算法操作,有边可连就连
		//flag=0 前部分操作一样,当选够了K条边与1相连的边以后就不再连接与1相连的边
		{
			ance[u]=v;
			ans[cnt++]=j;
			if(x[j]==1)
				captedge++;//统计1的度数
		}
	}
}
int main()
{
	while(scanf("%d%d%d",&n,&m,&k)!=EOF)
	{
		deg=0;
		l=(-1)*100000.0;
		r=100000.0;
		mid=0;
		for(int i=0;i<m;i++)
		{
			sig[i]=i;//Sig存储边的编号
			scanf("%d%d%d",&x[i],&y[i],&w[i]);
			if(x[i]>y[i])//存储边的起点和终点
				swap(x[i],y[i]);//X存储两者较小的,处理与1相连的边时方便
			if(x[i]==1)
				deg++;//统计与1相连边的度数
		}
		if(deg<k||(n>1&&k==0))//可选边<k必然不可能
		{//n>1时1的度数必然>=1,
			printf("-1\n");
			return 0;//不可能满足题意
		}
		cnt=0;
		Kruskal(1);//普通的跑一边算法
		if(cnt!=n-1)//不能构成生成树
		{
			printf("-1\n");
			return 0;
		}

		while((l+1e-5<r)&&(captedge!=k))//二分权重找答案
		{
			mid=(l+r)/2.0;
			Kruskal(1);
			if(captedge<k)
				r=mid;//减少权重,把与1相连的边前移
			else
				l=mid;//增加权重,把与1相连的边后移
		}
		if(captedge!=k)
			mid=(l+r)/2.0;//二分即可得到答案
		Kruskal(0);//Captedge=K时调整一下答案,!=K的时候就得到答案
		printf("%d\n",n-1);
		if(cnt>=1)
			printf("%d",ans[0]+1);
		for(int i=1;i<cnt;i++)
			printf(" %d",ans[i]+1);
		printf("\n");//输出格式有点乱,但CF不怎么看输出格式
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值