백준14510 Blazing New Trails

문제 링크

  • http://icpc.me/14510

문제 출처

  • 2017 NAIPC E번

사용 알고리즘

  • Alien Trick
  • MST

시간복잡도

  • $O(M \log M \log X)$

풀이

special space와 nonspecial space를 잇는 간선을 빨간색 간선, 그렇지 않은 간선을 파란색 간선이라고 합시다.
w개의 빨간색 간선을 사용했을 때의 MST를 구하는 문제가 됩니다.

그냥 정답을 구하는 것은 힘들 것 같으니까 Alien’s Trick을 생각해볼 수 있습니다.
빨간색 개수 조건을 없애고, 파란색 간선을 사용하는 비용을 C만큼 증가시킨 다음에 MST를 구해봅시다. C가 커질수록 빨간색 간선을 많이 사용할 것이고, C가 작아질수록 빨간색 간선을 적게 사용할 것입니다. 그렇다면, 이분 탐색으로 빨간색 간선을 정확히 w개 사용하는 C를 찾으면 문제를 풀 수 있습니다.

그러한 C가 항상 존재한다는 것에 대한 증명은 여기에서 볼 수 있습니다.

이분 탐색에서 탐색할 C의 범위를 X라고 한다면, 크루스칼 알고리즘을 log X번 수행하기 때문에 $O(M \log M \log X)$에 풀 수 있습니다.

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
#define all(v) v.begin(), v.end()
using namespace std;

typedef long long ll;

struct Edge{
    ll s, e, x, f;
    bool operator < (const Edge &t){
        if(x != t.x) return x < t.x;
        return f > t.f;
    }
};

struct UF{
    int par[202020];
    void clear(){ iota(par, par+202020, 0); }
    int find(int v){ return v == par[v] ? v : par[v] = find(par[v]); }
    bool merge(int u, int v){
        u = find(u), v = find(v);
        if(u == v) return 0;
        par[u] = v; return 1;
    }
} uf;

int n, m, k, w, cnt;
int chk[202020];
vector<Edge> edge, v;

ll mst = 0;
int go(ll c){
    uf.clear(); mst = 0;
    v = edge;
    for(auto &i : v) if(i.f) i.x += c;
    sort(all(v));
    int cnt = 0, t = 0;
    for(auto i : v){
        int s = i.s, e = i.e;
        if(uf.merge(s, e)){
            mst += i.x;
            cnt += i.f;
            t++;
            if(t == n - 1) return cnt;
        }
    }
    return cnt;
}

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m >> k >> w; edge.reserve(m);
    for(int i=0; i<k; i++){
        int t; cin >> t; chk[t] = 1;
    }
    for(int i=0; i<m; i++){
        ll s, e, x, f; cin >> s >> e >> x;
        if(chk[s] && !chk[e]) f = 1;
        else if(!chk[s] && chk[e]) f = 1;
        else f = 0;
        cnt += f;
        edge.push_back({s, e, x, f});
    }
    if(m < n-1 || cnt < w){ cout << -1; return 0; }
    uf.clear(); cnt = 0;
    for(auto i : edge) if(uf.merge(i.s, i.e)) cnt++;
    if(cnt != n - 1){ cout << -1; return 0; }
    ll l = -1e6, r = 1e6;
    ll ans = -1;
    while(l <= r){
        ll m = l + r >> 1;
        if(go(m) >= w){
            ans = mst - m * w;
            l = m + 1;
        }
        else r = m - 1;
    }
    cout << ans;
}