NAIPC 2017 E Blazing New Trails/bzoj 2654 二分 + 最小生成树

1.题意:给你n个点,m条边,让你选出一些边,保证选出来的边里面有k条边是连接特殊点和普通点的(以下称为特殊边),并且选出来的边能保证所有点互相联通。问这样的最小花费是多少。

2.分析:奇奇怪怪的题目,奇奇怪怪的做法

(1)最理想的状态肯定是恰好最小生成树并且里面有k条特殊边(最小生成树的最小花费 + k条特殊边)

(2)如果直接求最小生成树,我们是无法控制特殊边的数量的,但是如果我们给所有的特殊边同时加上或者减去一个值,可以控制特殊边在最小生成面里面的数量。控制了最小生成树,接下来就是判断特殊边的数量了。

<1>如果特殊边<k : 应该同时加的值再小一点,让特殊边多一点

<2>如果特殊边 >=k : 应该加的值再大一点,同时更新答案 ans = sum - k*add

3.代码:

#include<iostream>
#include<bits/stdc++.h>
#include<cstring>
#include<string>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 7;
struct Edge{
    int from,to,flag;
    LL val;
    bool operator <(const Edge&another)const{
        if(val==another.val)return flag>another.flag;
         return val < another.val;
    }
}edges[maxn*5],edge[maxn*5];
int pre[maxn*2],n,m,k,w;
bool judge[maxn*2];
int finded(int x){
    int r = x;
    while(pre[r]!=r)r = pre[r];
    int j = x;
    while(j!=r){
        int temp = pre[j];
        pre[j] = r;
        j = temp;
    }
    return r;
}
void Union(int a,int b){
   int fx = finded(a);
   int fy = finded(b);
   if(fx!=fy){
       pre[fx] = fy;
   }
}
int Kruskal(LL v,LL &sum){
    sum = 0;
    for(int i = 0;i<=n;i++)pre[i] = i;
    for(int i = 0;i<m;i++){
        edges[i] = edge[i];
        if(edge[i].flag){//所有特殊边 + v
            edges[i].val+=v;
        }
    }
    sort(edges,edges+m);
    int lens = 0,l = 0;
    for(int i = 0;i<m;i++){//求最小生成树
        int fa = finded(edges[i].from);
        int fb = finded(edges[i].to);
        if(fa!=fb){
            Union(edges[i].from,edges[i].to);
            lens++;
            sum+=edges[i].val;
            if(edges[i].flag)l++;
        }
        if(lens==n-1)break;
    }
    if(lens!=n-1)return -1;
    return l;
}
int main(){
    memset(judge,0,sizeof(judge));
    scanf("%d%d%d%d",&n,&m,&k,&w);
    int len = 0;
    for(int i = 1;i<=k;i++){
        int x;
        scanf("%d",&x);
        judge[x] = 1;//输入特殊点
    }
    for(int i = 0;i<m;i++){
        scanf("%d%d%lld",&edge[i].from,&edge[i].to,&edge[i].val);
        if((judge[edge[i].from]&&!judge[edge[i].to])||(!judge[edge[i].from]&&judge[edge[i].to])){//判断特殊边
            edge[i].flag = 1;
            len++;
        }
        else edge[i].flag = 0;
    }
    if(m<n-1||len<w){//无解
        printf("-1\n");
    }
    else{//否则二分
         LL r = maxn,l = -maxn;//二分加在特殊边上的值,控制特殊边的数量
         LL ans = -1;
         LL res;
         while(l<=r){
            LL mid = (l + r)/2;
            int temp = Kruskal(mid,res);
            if(temp==-1){//无法任意两点联通
                printf("-1\n");
                return 0;
            }
            if(temp>=w){//特殊边数目 >= k
               ans = res - w*mid;
               l = mid+1;
            }
            else r = mid-1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/82964973