https://www.acmicpc.net/problem/1626

 

1626번: 두 번째로 작은 스패닝 트리

첫째 줄에 그래프의 정점 수 V(1 ≤ V ≤ 50,000)와 에지 수 E(1 ≤ E ≤ 200,000)가 들어온다. 둘째 줄부터 E+1번째 줄까지 한 에지로 연결된 두 정점과 그 에지의 가중치가 주어진다. 음수 가중치는 없으며, 답은 int 범위를 넘지 않는다. 정점 번호는 1보다 크거나 같고, V보다 작거나 같은 자연수이다.

www.acmicpc.net


이 문제를 해결 하기 위해서는 우선 밑의 문제를 풀 수 있어야 한다.

https://www.acmicpc.net/problem/15481

 

15481번: 그래프와 MST

첫째 줄에 정점의 개수 N과 간선의 개수 M (2 ≤ N ≤ 200,000, N-1 ≤ M ≤ 200,000) 둘째 줄부터 M개의 줄에 간선의 정보 u, v, w가 주어진다. u와 v를 연결하는 간선의 가중치가 w라는 뜻이다. (1 ≤ u, v ≤ n, u ≠ v, 1 ≤ w ≤ 109) 

www.acmicpc.net

위의 문제와 비슷한 아이디어로 풀긴 하지만 좀 더 까다롭다.

(초기 MST를 구성하면서, 모든 정점을 방문하지 못한다면, -1를 출력하면 된다.)

{u , v , w} 의 정보를 갖는 새로운 간선을 삽입 할 경우

(기존 mst의 간선의 총합)-(u와v의 간선중 가장 큰 경로)+(w)

가 기존 (기존 mst의 간선의 총합) 보다 큰 것중에 가장 작은 값이 곧 답이 된다.

그러나, (u와v의 간선중 가장 큰 경로) == (w)

인경우를 생각 해보면 결국,

(기존 mst의 간선의 총합)-(u와v의 간선중 가장 큰 경로)+(w) == (기존 mst의 간선의 총합)

이 되어버리기 때문에, 실제로는 다른 mst가 존재함에도 불구하고 conitnue 하게 된다.

그렇기 때문에, 첫번째로 큰 간선이 w와 같은 경우에

두번째로 큰 간선을 찾아서 똑같이 적용시키면 된다.


#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <limits.h>
#include <cstring>
using namespace std;

#define MAX_N 50001
#define MAX_PAR 20
#define SWAP(a,b) {int t= b; b = a; a = t;}
typedef long long ll;

int par[MAX_N][MAX_PAR]; // 부모 배열
int MAX[MAX_N][MAX_PAR]; // 최댓값 배열
int depth[MAX_N]; // 깊이 배열
bool visit[MAX_N]; // DFS 체크

// 입력 순서대로 우선 저장될 edge 정보
class edge {
public:
     int u, v, w;
     edge() {}
     edge(int u, int v, int w) {
          this->u = u;
          this->v = v;
          this->w = w;
     }
};
vector<edge> edges; // 입력 순서대로 저장

// MST를 위한 간선
vector<pair<int, int>> map[MAX_N];

// PQ에 들어갈 아이템 ( cost 순으로 정렬 됨 )
class PQItem {
public:
     int now, nowDepth, prev, prevDist;
     PQItem() {}
     PQItem(int now, int nowDepth, int prev, int prevDist) {
          this->now = now;
          this->nowDepth = nowDepth;
          this->prev = prev;
          this->prevDist = prevDist;
     }
     bool operator < (const PQItem & b) const {
          return this->prevDist < b.prevDist;
     }
     bool operator > (const PQItem & b) const {
          return this->prevDist > b.prevDist;
     }
};
priority_queue<PQItem, vector<PQItem>, greater<> > pq;

// 입력받을 V, E
int V, E;
ll MST() {
     int cnt = 0, ret = 0; 
     pq.push(PQItem(1, 0, 1, 0)); // 1번노드부터 pq push
     while (!pq.empty()) {
          PQItem top = pq.top(); pq.pop();
          int now = top.now;
          int prev = top.prev;
          int prevDist = top.prevDist;
          int nowDepth = top.nowDepth;
          if (visit[now]) continue;
          par[now][0] = prev; // 1번째 부모
          MAX[now][0] = prevDist; // 부모와의 dist
          depth[now] = nowDepth; // 현재 depth
          ret += prevDist; // mst 간선의 총합을 위한 리턴값
          visit[now] = true; // visit 체크
          cnt++; // MST의 존재여부 파악을 위함
          for (int i = 0; i < map[now].size(); ++i) {
               if (!visit[map[now][i].first]) {
                    pq.push(PQItem(map[now][i].first, nowDepth + 1, now, map[now][i].second));
               }
          }
     }
     if (cnt != V) ret = -1; // mst가 존재하지않는다 ( = 한 정점에서 모든 정점을 방문 할 수 없다 )
     return ret;
}

// lca 알고리즘
int LCA(int x, int y) {
     if (depth[x] > depth[y]) SWAP(x, y);

     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}

// x노드와 y노드 사이의 간선의 최댓값 리턴
int getMax(int x, int y) {
     int lca = LCA(x, y);
     int ret = -1;
     int x_to_lca = depth[x] - depth[lca];
     int y_to_lca = depth[y] - depth[lca];
     for (int i = 19; i >= 0; i--) {
          if (x_to_lca >= (1 << i)) {
               ret = max(ret, MAX[x][i]);
               x_to_lca -= (1 << i);
               x = par[x][i];
          }
     }

     for (int i = 19; i >= 0; i--) {
          if (y_to_lca >= (1 << i)) {
               ret = max(ret, MAX[y][i]);
               y_to_lca -= (1 << i);
               y = par[y][i];
          }
     }
     return ret;
}

// x노드와 y 노드 사이의 2번째로 큰 ( prevMax보다 첫번째로 작은 ) 값 리턴
// O(N)으로 탐색했음 ( 더좋은 방법이 있을 것 같음 )
int getSecondMax(int x, int y, int prevMax) {
     int lca = LCA(x, y);
     int x_to_lca = depth[x] - depth[lca];
     int y_to_lca = depth[y] - depth[lca];
     int ret = -1;
     for (int i = 0; i < x_to_lca; ++i) {
          if (MAX[x][0] < prevMax) {
               ret = max(ret, MAX[x][0]);
          }
          x = par[x][0];
     }

     for (int i = 0; i < y_to_lca; ++i) {
          if (MAX[y][0] < prevMax) {
               ret = max(ret, MAX[y][0]);
          }
          y = par[y][0];
     }
     return ret;
}

int main() {
     ios::sync_with_stdio(false);
     cin.tie(0); cout.tie(0);
     cin >> V >> E;
     for (int i = 0; i < E; ++i) {
          int a, b, w; cin >> a >> b >> w;
          edges.push_back(edge(a, b, w));
          map[a].push_back({ b,w });
          map[b].push_back({ a,w });
     }

     ll minimum = MST();
     if (minimum == -1) {
          cout << -1 << "\n";
          return 0;
     }

     for (int i = 1; i <= 19; ++i) {
          for (int j = 1; j <= V; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
               MAX[j][i] = max(MAX[j][i - 1], MAX[par[j][i - 1]][i - 1]);
          }
     }

     int ans = INT_MAX;
     for (int i = 0; i < E; ++i) {
          int u, v, w;
          u = edges[i].u;
          v = edges[i].v;
          w = edges[i].w;
          int maxE = getMax(u, v);
          // 가장 큰 값과 비교
          if (minimum - maxE + w > minimum) {
               if (ans > minimum - maxE + w) ans = minimum - maxE + w;
          }
          // 가장 큰 값이 현재 삽입하려는 간선의 weight와 같다면
          // 두번 째로 큰 간선을 찾는다
          else if (minimum - maxE + w == minimum) {
               int secondMaxE = getSecondMax(u, v, maxE);
               // 찾았 다면
               if (secondMaxE != -1) {
                    if (ans > minimum - secondMaxE + w) ans = minimum - secondMaxE + w;
               }
          }
     }
     if (ans == INT_MAX) cout << -1 << "\n";
     else cout << ans << "\n";
}

'알고리즘 > [C++]BOJ' 카테고리의 다른 글

[백준BOJ] 10999 구간 합 구하기 2  (0) 2019.10.04
[백준BOJ] 15481 그래프와 MST  (0) 2019.10.03
[백준BOJ] 15480_LCA와 쿼리  (0) 2019.10.03

+ Recent posts