1. 程式人生 > >NAIPC2017 E - Blazing New Trails (二分 + 最小生成樹)

NAIPC2017 E - Blazing New Trails (二分 + 最小生成樹)

題目連結

給出一個圖,對於其中一些確定的邊,可以將它們的權值都加上某一個值,使得這些邊中正好有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;
}