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 |