给出一个图,对于其中一些确定的边,可以将它们的权值都加上某一个值,使得这些边中正好有w条出现在最小生成树中。求最小生成树的最小总权值。
可以发现,对于加到特殊边上的值,它和最小生成树中特殊边的数量是一个单调的关系。因此可以二分这个值,然后每次去求最小生成树。
实现起来主要是一些细节的问题。在合法的解中,这个值与最小生成树的值也是一个单调的关系,可以相应的简化一下记录的过程。
以及这个东西完全没有必要用double去做,反而会产生某些精度的问题。
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int maxn = 200050;
const int maxm = 500050;
int n, m, x, k, w, cnt, num;
ll res;
int pre[maxn], flag[maxn];
struct node
{
int u, v;
ll w;
int vis;
}e[maxm];
bool cmp(node a, node b)
{
if(a.w != b.w) return a.w < b.w;
return a.vis > b.vis;
}
int Find(int x)
{
if(x == pre[x]) return x;
return pre[x] = Find(pre[x]);
}
void kruskal(ll x)
{
for(int i = 1;i <= m;i++)
{
if(e[i].vis == 1)
e[i].w = e[i].w + x;
}
for(int i = 0;i <= n;i++) pre[i] = i;
sort(e+1, e+m+1, cmp);
cnt = num = res = 0;
for(int i = 1;i <= m;i++)
{
int fu = Find(e[i].u);
int fv = Find(e[i].v);
if(fu != fv)
{
if(e[i].vis == 1) num++;
pre[fu] = fv;
cnt++;
res += e[i].w;
if(cnt >= n-1) break;
}
}
for(int i = 1;i <= m;i++)
{
if(e[i].vis == 1)
e[i].w = e[i].w - x;
}
if(cnt != n-1) num = -1;
}
int main()
{
scanf("%d%d%d%d", &n, &m, &k, &w);
memset(flag, 0, sizeof(flag));
for(int i = 1;i <= k;i++)
{
scanf("%d", &x);
flag[x] = 1;
}
int cnt = 0;
for(int i = 1;i <= m;i++)
{
scanf("%d%d%lld", &e[i].u, &e[i].v, &e[i].w);
if(flag[e[i].u] + flag[e[i].v] == 1)
e[i].vis = 1, cnt++;
else e[i].vis = 0;
}
if(cnt < w || m < n-1) {puts("-1"); return 0;}
ll L = -INF, R = INF, ans = -1;
while(L <= R)
{
ll mid = (L + R)/2;
kruskal(mid);
if(num < 0) {puts("-1"); return 0;}
if(num >= w) ans = res - mid*w, L = mid + 1;
else R = mid - 1;
}
printf("%lld\n", ans);
return 0;
}