티스토리 뷰

문제: icpc.me/1626


두 번째로 작은 스패닝 트리를 구하는 문제이다.


우선 정점이 V개 간선이 E개인 그래프에서 MST를 구해준다.


그리고 MST를 구성할 때 사용하지 않은 E-V+1개의 간선에 대해서 해당 간선을 포함하는 MST를 구하여 답을 구해낸다..


하지만 E-V+1번 크루스칼을 돌린다면 TLE를 보게 될것이다.


그렇다면 E-V+1개의 간선을 각각 포함하는 MST를 어떻게 구해낼 수 있을까?


우선 현재 보고 있는 간선이 1번 정점과 5번 정점을 연결한다고 가정해보자.


그렇다면 우리는 기존의 MST에서 1번 정점과 5번 정점을 연결해주어 N개의 간선을 가진 그래프를 생각해보자.


해당 그래프를 트리로 만드려면 하나의 간선을 제거해야한다. 이 때 제거되야 되는 간선은 1번 정점과 5번 정점을 연결하는 경로에 있는 간선들 중 하나일 것이다.


따라서 우리는 E-V+1개의 간선들에 대해서 기존 MST값 + 해당 간선의 값 - V1정점에서 V2정점으로의 경로에 있는 간선의 값중 최댓값들을 구해주면 된다.


하지만 V1 정점에서 V2 정점으로의 경로 중 최댓값을 선형으로 탐색할 경우 최악의 경우 O(V)의 시간복잡도를 가지게 된다.


이렇게 구하게 될 경우 역시 TLE를 보게된다.


따라서 우리는 V1 정점에서 V2 정점으로의 경로 중 최댓값을 빠르게 구해내야하는데 


MST에서의 경로이므로 트리라는 조건이기 때문에 LCA를 이용하여 가능하다. 


LCA를 구하기 위한 2^N번째 부모를 저장할 때 2^N번째 간선까지의 최댓값을 같이 저장하여 준다면 O(logV)시간에 경로의 최댓값을 구해낼 수 있다.


이제 답을 구할 수 있겠다고 생각 되겠지만 안타깝게도 또 처리해주어야 하는 부분이 존재한다...


예를들어 아까의 경우 1번 정점과 5번 정점을 잇는 간선의 가중치가 8이라고 해보자.


그리고 LCA를 이용하여 얻어낸 간선의 경로 중 최댓값이 8이라고 한다면 우리는 기존 MST와 똑같은 값을 답으로 구해낼 것이다.


하지만 이 경우는 답이되면 안되기 때문에 이런 경우를 차단해주어야 한다.


그렇다면 LCA를 구하는 도중에 간선의 가중치가 내가 현재 보는 간선의 가중치와 같을 경우에 그 경로의 간선들을 또 탐색해내어야 하는데


이걸 또 선형으로 구한다면 TLE를 보게 될테니 이진 탐색을 이용하여서 구해내어야 한다.


조금 복잡하니 소스코드로 확인하도록 하자.


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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
int v, e, p[50050], c[200020], par[50050][22], g[50050][22], h[50050], x, y, z, f, cnt;
vector<pair<int, pair<intint>>> vt;
vector<vector<pair<intint>>> mst;
int find(int h){
    return h == p[h] ? h : p[h] = find(p[h]);
}
bool merge(int x, int y, int z) {
    if (find(x) == find(y))return false;
    mst[x].push_back({ y ,z });
    mst[y].push_back({ x ,z });
    f += z;
    p[find(x)] = find(y);
    return true;
}
void dfs(int curr, int prev) {
    for (auto next : mst[curr]) {
        if (next.first == prev)continue;
        h[next.first] = h[curr] + 1;
        par[next.first][0= curr;
        g[next.first][0= next.second;
        dfs(next.first, curr);
    }
}
int bsch(int x, int y, int z) {
    if (y == 0)return 0;
    int ret = 0;
    if (g[x][y - 1== z) 
        ret = max(ret, bsch(x, y - 1, z));
    else
        ret = max(ret, g[x][y - 1]);
    if (g[par[x][y - 1]][y - 1== z)
        ret = max(ret, bsch(par[x][y - 1], y - 1, z));
    else
        ret = max(ret, g[par[x][y - 1]][y - 1]);
    return ret;
}
int getmax(int x, int y, int z) {
    if (h[x] > h[y])
        swap(x, y);
    int ret = 0;
    for (int i = 20; i >= 0; i--) {
        if ((h[y] - h[x]) >= (<< i)) {
            if (g[y][i] == z)
                ret = max(ret, bsch(y, i, z));
            else
                ret = max(ret, g[y][i]);
            y = par[y][i];
        }
    }
    if (x == y)return ret;
    for (int i = 20; i >= 0; i--) {
        if (par[x][i] != par[y][i]) {
            if (g[x][i] == z)
                ret = max(ret, bsch(x, i, z));
            else
                ret = max(ret, g[x][i]);
            if (g[y][i] == z)
                ret = max(ret, bsch(y, i, z));
            else
                ret = max(ret, g[y][i]);
            x = par[x][i];
            y = par[y][i];
        }
    }
    if (g[x][0!= z)
        ret = max(ret, g[x][0]);
    if (g[y][0!= z)
        ret = max(ret, g[y][0]);
    return ret;
}
int main() {
    scanf("%d%d"&v, &e);
    mst.resize(v + 1);
    for (int i = 1; i <= v; i++)
        p[i] = i;
    for (int i = 0; i < e; i++) {
        scanf("%d%d%d"&x, &y, &z);
        vt.push_back({ z,{x,y} });
    }
    sort(vt.begin(), vt.end());
    for (int i = 0; i < e; i++) {
        if (merge(vt[i].second.first, vt[i].second.second, vt[i].first)) {
            c[i]++;
            cnt++;
        }
        if (cnt == v - 1)
            break;
    }
    if (cnt != v - || e <= v - 1) {
        puts("-1");
        return 0;
    }
    dfs(10);
    for (int j = 1; j < 21; j++) {
        for (int i = 1; i <= v; i++) {
            par[i][j] = par[par[i][j - 1]][j - 1];
            g[i][j] = max(g[par[i][j - 1]][j - 1], g[i][j - 1]);
        }
    }
    ll r = 1e11;
    for (int i = 0; i < e; i++) {
        if (c[i])continue;
        int gmax = getmax(vt[i].second.first, vt[i].second.second, vt[i].first);
        if (!gmax)continue;
        r = min(r, (ll)(f - gmax + vt[i].first));
    }
    if (r == 1e11 || r == f)puts("-1");
    else
        printf("%lld\n", r);
    return 0;
}
cs


'알고리즘 관련 > BOJ' 카테고리의 다른 글

BOJ)10319 좀비 아포칼립스  (0) 2017.06.12
BOJ)5651 완전 중요한 간선  (0) 2017.06.12
BOJ)1626 두 번째로 작은 스패닝 트리  (5) 2017.06.12
BOJ)10881 프로도의 선물 포장  (0) 2017.06.11
BOJ)11689 GCD(n,k) = 1  (1) 2017.06.10
BOJ)2503 숫자 야구  (1) 2017.06.08
댓글
  • 프로필사진 khj94811 덕분에 성공했습니다 감사합니다 2017.08.27 16:45
  • 프로필사진 hotdog 덕분에 성공했습니다 감사합니다! 2017.11.06 23:59
  • 프로필사진 weasel 좋은 포스팅 감사합니다.
    궁금증이 생겼는데, 길이가 같은 간선일때 처리하는 부분에서 처음부터 경로상의 max길이를 두번째 긴 길이(첫번째보다 strictly 작은) 까지 저장하고 이를 이용하는 방식으로 구현하는건 어떻게 생각하시나요??
    이진탐색으로 처리하는 과정이 잘 이해가 안가서요 ㅠㅠ
    2020.04.11 18:07
  • 프로필사진 EWE 님꺼로 하면 틀렸다고 나와요 2020.07.08 12:36
  • 프로필사진 와 미치겠다 3 3
    1 2 0
    2 3 1
    1 3 1
    2
    가 나와야 하는데 -1이 나옵니다. 이 소스는 틀렸습니다.
    2020.08.02 14:03
댓글쓰기 폼