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;
}