문제 링크: https://www.acmicpc.net/problem/17414
문제 제목이 '세빈은 오일러 회로를 좋아해'입니다.
오일러 회로 복습한김에 쉬운 오일러 회로, 트레일 문제좀 몇 개 풀어보려고 했는데 낚였습니다. 빡세네요.
일단 이 문제의 조건을 유심히 보셔야 합니다. 안그러면 저처럼 삽질하거든요
- 1. N개의 정점을 모두 방문해야 한다
- 2. [서브태스크 2]
일단 모든 정점을 방문해야 하니까 degree == 0인 노드도 연결해야 한다는 점을 명심하시고요
서브태스크 2가 좀 중요한데, 연결그래프가 아닌 그래프, 즉 여러 컴포넌트로 나뉜 그래프가 입력으로 주어질 수 있는 점을 고려해야 합니다.
서브태스크 1만 풀려면 그냥 홀수점 쭉 찾아서 두개씩 묶어서 출력하면 됩니다. (홀수점의 개수는 항상 짝수개입니다)
제 풀이방법은 이렇습니다.
- 그래프를 컴포넌트로 분리. 이때 컴포넌트의 홀수점과 짝수점을 분리
- 우선순위 큐를 만듦. 이때 각 컴포넌트의 홀수점의 개수가 우선순위가 되도록 만듦
{컴포넌트의 홀수 점 개수, 컴포넌트의 인덱스}를 우선순위 큐에 넣음 - 컴포넌트가 하나가 될때까지 우선순위 큐에서 컴포넌트 두개를 빼서 하나로 합침
- 컴포넌트 사이에 홀수점-홀수점, 홀수점-짝수점, 짝수점-짝수점 순의 우선순위로 1개의 간선을 추가
- 큰 거에 작은거를 합쳐야 시간복잡도가 O(NlgN)가 된다고 함. 안그러면 O(N^2)
아직 안배운 부분이라 증명은 못하겠네요;; -> 참고 링크
- 컴포넌트가 하나가 되면 남은 홀수점 두개씩 묶어서 출력
컴포넌트가 1개가 될 때까지 합치는게 핵심입니다.
대충 맞는 것 같은데 자꾸 시간 초과가 나길래 검색해봤더니 배열 두 개를 합칠 때 큰 배열에 작은 배열을 추가하는 식으로 해야 시간복잡도가 줄어든다고 하네요. 지금 생각해보면 당연한건데도 이런 생각을 못했다니 참 씁슬합니다. 공부가 부족한 것 같습니다.
#pragma GCC optimize ("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,avx,avx2")
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> graph;
vector<int> degree;
vector<bool> visited;
int n, m;
vector<pair<int, int>> to_add;
void dfs(int current, vector<int>& order) {
visited[current] = true;
for (auto& next : graph[current])
if (!visited[next])
dfs(next, order);
order.push_back(current);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
#endif
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
degree.resize(n);
graph.resize(n);
for (int i = 0; i < m; ++i) {
int a, b;
cin >> a >> b;
--a; --b;
graph[a].push_back(b);
graph[b].push_back(a);
++degree[a];
++degree[b];
}
visited.resize(n);
typedef pair<vector<int>, vector<int>> component; // 해당 component에 있는 <홀수점, 짝수점>
vector<component> components;
priority_queue< pair<int, int> > pq; // <홀수점 개수, 컴포넌트 번호>
for (int i = 0; i < n; ++i) {
if (!visited[i]) {
vector<int> order;
dfs(i, order);
vector<int> odd_idxs, even_idxs;
for (auto& idx : order) {
if (degree[idx] % 2 == 1)
odd_idxs.push_back(idx);
else
even_idxs.push_back(idx);
}
pq.emplace(odd_idxs.size(), components.size());
components.emplace_back(move(odd_idxs), move(even_idxs));
}
}
while (!pq.empty()) {
if (pq.size() == 1) {
break;
}
auto [c1_odd_cnt, c1_idx] = pq.top(); pq.pop();
auto [c2_odd_cnt, c2_idx] = pq.top(); pq.pop();
auto& c1 = components[c1_idx], &c2 = components[c2_idx];
auto& c1_odds = c1.first, &c2_odds = c2.first;
auto& c1_evens = c1.second, &c2_evens = c2.second;
// merge two components
//
// a b
//1 - 홀수점 - 홀수점
//2 - 홀수점 - 짝수점
//3 - 짝수점 - 짝수점
int a, b;
if (c1_odd_cnt && c2_odd_cnt) {
a = c1_odds.back();
b = c2_odds.back();
c1_odds.pop_back();
c2_odds.pop_back();
}
else if (c1_odd_cnt) {
a = c1_odds.back();
b = c2_evens.back();
c1_odds.pop_back();
c2_odds.push_back(b);
c2_evens.pop_back();
}
else {
a = c1_evens.back();
b = c2_evens.back();
c1_odds.push_back(a);
c2_odds.push_back(b);
c1_evens.pop_back();
c2_evens.pop_back();
}
to_add.emplace_back(a, b);
++degree[a]; ++degree[b];
// 큰거에 작은거를 합침
int c1_whole_cnt = c1_odds.size() + c1_evens.size();
int c2_whole_cnt = c2_odds.size() + c2_evens.size();
if (c1_whole_cnt > c2_whole_cnt) {
move(c2_odds.begin(), c2_odds.end(), back_inserter(c1_odds));
move(c2_evens.begin(), c2_evens.end(), back_inserter(c1_evens));
pq.emplace(c1_odds.size(), c1_idx);
}
else {
move(c1_odds.begin(), c1_odds.end(), back_inserter(c2_odds));
move(c1_evens.begin(), c1_evens.end(), back_inserter(c2_evens));
pq.emplace(c2_odds.size(), c2_idx);
}
}
// 다 연결된 component 내부에 홀수점끼리 연결
vector<int> odds;
for (int i = 0; i < n; ++i) {
if (degree[i] % 2)
odds.push_back(i);
}
for (int i=0; i<odds.size(); i+=2)
to_add.emplace_back(odds[i], odds[i+1]);
if (to_add.empty())
cout << "0\n" << '\n';
else {
cout << to_add.size() << '\n';
for (auto& [from, to] : to_add)
cout << from+1 << ' ' << to+1 << '\n';
}
return 0;
}
맞은 사람 소스 보니까 Union Find로 푼 사람이 많던데 아직 그 알고리즘 공부를 안해서 동일한 풀이인지 아닌지는 모르겠네요. 유니온 파인드 공부하면 한번 더 풀어봐야겠습니다.
반응형
'Online Judge > 백준' 카테고리의 다른 글
[백준][C++] 10539: 수빈이와 수열 (0) | 2020.05.18 |
---|---|
[백준][C++] 3954: Brainf**k 인터프리터 (0) | 2020.05.14 |
[백준][C++] 1463: 1로 만들기 (0) | 2020.05.13 |
[백준][C++] 17825: 주사위 윷놀이 (0) | 2020.05.12 |
[백준][C++] 13458: 시험 감독 (0) | 2020.05.11 |