NAIPC2017 E - Blazing New Trails (二分 + 最小生成樹)
阿新 • • 發佈:2018-11-10
給出一個圖,對於其中一些確定的邊,可以將它們的權值都加上某一個值,使得這些邊中正好有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; }