Codeforces 125E MST Company(k度限制生成树)

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

There are n cities in Berland, some pairs of the cities are connected by roads. Each road has its price. One can move along any road in any direction. The MST team should carry out the repair works on some set of roads such that one can get from any city to any other one moving only along the repaired roads. Moreover, this set should contain exactly k capital roads (that is, the roads that start or finish in the capital). The number of the capital is 1.

As the budget has already been approved, the MST Company will profit by finding the set with minimum lengths of roads.

Input
The first input line contains three integers n, m, k (1 ≤ n ≤ 5000;0 ≤ m ≤ 105;0 ≤ k < 5000), where n is the number of cities in the country, m is the number of roads in the country, k is the number of capital roads in the required set. Then m lines enumerate the roads in question. Each road is specified by three numbers ai, bi, wi (1 ≤ ai, bi ≤ n; 1 ≤ w ≤ 105), where ai, bi are the numbers of cities linked by a road and wi is its length.

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

Output
In the first line print the number of roads in the required set. The second line should contain the numbers of roads included in the sought set. If the sought set does not exist, print -1.

Example
Input
4 5 2
1 2 1
2 3 1
3 4 1
1 3 3
1 4 2
Output
3
1 5 2

题意:n个顶点m条边,求节点1恰好为k度的最小生成树

题解:这个是比较标准的单点k度限制生成树问题,唯一不同的是这题要节点1度数恰好等于k,下面简单介绍两种方法。

方法一:将除节点1以外的所有点做最小生成树,我们会得到x个联通块(x>=1),每个连通块中选出1个与节点1距离最近的点,然后把他们与节点1相连,这样会得到节点1的x度最小生成树(当然得不到就无解啦0.0),然后遍历与节点1相连的不在这个生成树中的边,找到“最优边”(所谓“最优边”,就是如果把这条边加入生成树中会得到1个环,这个环里面的最大边与这当前选的边差值最大),找到最优边后,将这个最优边添加进去,然后删掉这个环里面的最大边,就得到了节点1的x+1度最小生成树,像这样重复遍历k-x次就可以得到节点1的k度最小生成树了。现在难度就是求环里面的最大边了,其实不难,每次遍历之前,从节点1开始搜一遍就可以预处理出每个点到节点1的最大边(实在不会的,请自行百度吧)。其实这个方法与普通的单点k度限制的非常相识,唯一不同的是这个要实实在在的遍历k-x次,而普通的的单点k度限制遍历到环里最大边小于“最优边”就可以了。

上面这个方法可能复杂度有点高(不过还是可以过的)。

方法二:试想一下,最优解里面不与节点1相连的边可以看成所有不与节点1相连的边按长度从小到大排序后选出的一部分边,并且最优解里面与节点1相连的边可以又看成所有与节点1相连的边按长度从小到大排序后选出的一部分边,也就是所有边可以分成这两类,这两类边各自内部的挑选顺序是确定的,但两种不同类的边的挑选顺序应该是怎么确定呢,我们可以把与节点1相连的边全部加上某个值(可正可负)(假装加用来整体影响这类边在另一类边排序的相对位置,实际并没有真正改变边的值,这里一定要理解!),当加一个很小的负值时,与节点1相连的边将全部排在不与1相连的边的前面,这样得到的最小生成树的节点1的度>k,同理当加一个很大的正值时,得到的最小生成树的节点1的度<k,所以我们二分要加的这个值,就可以得出答案。这里面有非常非常非常多的坑!CF的数据不给力让很多人水过了,比如下面两组数据:

输入1:

4 5 2

1 2 1

1 3 1

1 4 1

2 3 1

4 3 1

这组数据坑点:我们设加的值为sign,sign<0时k=3>2,sign>0时k=1<2,sign=0时k可能等于1或2或3,如果运气不好你可能永远也分不到,k=2的时候,如果你人品差,当分到sign=0时算出k=3,你之后就会一直分sign>0的部分,但那部分k永远等于1<2,这个时候就永远也得不到正解了。

正确处理方法:二分区间[1e-5 , 1e-5+0.3],这样就不会分到整数了,还有二分的正确边界为:k==2或l+0.1>r&&k>2(想想为什么)。

输入2:

5 4 2

1 2

1 3

1 4

1 5

这个数据的坑点就比较明显了,但CF好像没有这组数据0.0。

正确处理方法:除去节点1外做个最小生成树,如果连通块个数>k,就说明无解。

AC代码(这个代码并没有解决输入2的坑点,那就留个聪明的你来解决她吧微笑

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=5005,M=100005;
struct Edge
{
    int a,b,len;
}edge[M];
int id[M],fa[N],chun[M];
int n,m,k,num_1,num_all,num_chun;
double sign;

inline bool cmp(const int &a,const int &b)///核心
{
    return sign*(edge[a].a==1)+edge[a].len<sign*(edge[b].a==1)+edge[b].len;
}

int Fa(int x)///并查集
{
    return fa[x]?fa[x]=Fa(fa[x]):x;
}

void chuli(int flag)///最小生成树
{
    for(int i=1;i<=n;i++)
        fa[i]=0;
    sort(id+1,id+1+m,cmp);
    int a,b,aa,bb;
    num_1=num_all=num_chun=0;
    for(int i=1;i<=m;i++)
    {
        a=edge[id[i]].a,b=edge[id[i]].b;
        aa=Fa(a),bb=Fa(b);
        if(aa==bb)continue;
        if(num_1+(a==1)>k&&flag)continue;
        fa[aa]=bb;
        if(a==1)num_1++;
        num_all++;
        chun[++num_chun]=id[i];
        if(num_all==n-1)return;
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    int a,b,len;
    int tot=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&len);
        if(a>b)swap(a,b);
        edge[i]={a,b,len};
        if(a==1)tot++;
    }
    if(tot<k||(n>1&&k==0))
    {
        printf("-1\n");
        return 0;
    }
    sign=0;
    for(int i=1;i<=m;i++)
        id[i]=i;
    chuli(0);
    if(num_all<n-1)
    {
        printf("-1\n");
        return 0;
    }
    double l=-1e5,r=1e5+0.3,mid;
    while(1)
    {
        if(num_1==k)break;
        if(l+0.1>r&&num_1>k)break;
        mid=(l+r)/2;
        sign=mid;
        chuli(0);
        if(num_1<k)r=mid;
        else l=mid;
    }
    if(num_1!=k)chuli(1);
    printf("%d\n",num_chun);
    for(int i=1;i<=num_chun;i++)
        printf("%d%c",chun[i],i==num_chun?'\n':' ');
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值