Divide & Conquer은 분할 정복을 뜻합니다. 분할 정복은 큰 문제를 작은 문제로 분할하여 푼다는 관점에서 DP와 유사합니다. 하지만 DP에서 나타나는 특징인 overlapping subproblem이 나타나지 않습니다. 분할 정복은 크게 아래에 있는 세 가지 단계로 나눌 수 있습니다.
분할 정복 문제들은 일반적으로 난이도의 편차가 크고, 주로 쉬운 알고리즘 문제로 출제되거나 다른 알고리즘과 섞여서 출제된다고 합니다.
분할 정복을 사용하는 예시
Binary Search는 이분 탐색을 뜻합니다. 이분 탐색은 정렬된 배열에서 특정 원소를 찾을 때 사용됩니다.(정렬된 배열이라는 점이 중요합니다!) 이분 탐색은 𝑂(log 𝑁)의 시간 복잡도를 가집니다.
배열을 정렬(정렬되어 있지 않은 경우)
양 끝 점을 기준으로 정함
두 개의 기준점을 이용해 중앙값을 구함
중앙값을 탐색하고자 하는 값과 비교
4-1. 탐색하고자 하는 값보다 중앙값이 크다면 중앙값보다 크거나 같은 모든 값은 탐색을 할 필요X
4-2.탐색하고자 하는 값보다 중앙값이 작다면 중앙값보다 작거나 같은 모든 값은 탐색할 필요X
탐색할 필요가 없는 범위를 제거하고 다시 끝 기준점을 정함
값을 찾을 떄까지 3~5번 반복
#include <bits/stdc++.h>
using namespace std;
int arr[15] = { 1, 3, 4, 6, 7, 8, 10, 12 ,15, 17, 19, 20, 21, 23, 24 };
int binarySearch(int seq[], int num) {
int l = 0;
int r = sizeof arr / sizeof arr[0];
while (l <= r) {
int mid = (l + r) / 2;
if (num == seq[mid])
return mid;
if (num < seq[mid])
r = mid - 1;
else
l = mid + 1;
}
return -1;
}
int main() {
int n;
scanf_s("%d", &n);
int result = binarySearch(arr, n);
printf("%d", result);
return 0;
}
upper_bound(vector.begin(), vector.end(), searchNum) - lower_bound(vector.begin(), vector.end(), searchNum)
나머지 정리와 분할 정복을 함께 사용해서 풀 수 있는 문제입니다. a의 n승을 봤을 때, n%2 = 0일 경우 a의 n/2승 2개를 곱해줬다고 볼 수 있습니다. 또한 n%2 = 1일 경우는, a에 a의 n-1승을 곱한 수로 볼 수 있습니다. 이렇게 a의 n승을 a의 n/2승 또는 a의 n-1승의 소문제로 만들어서 풀면 됩니다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll pow(ll a, ll b, ll c) {
if (b <= 0)
return 1 % c;
if (b == 1)
return a % c;
if (b % 2)
return ((a % c) * pow(a, b - 1, c) % c) % c; // 나머지 정리 이용
else {
ll res = pow(a, b / 2, c) % c;
return res * res % c; // 나머지 정리 이용
}
}
int main() {
ll a, b, c;
scanf_s("%lld %lld %lld", &a, &b, &c);
printf("%lld", pow(a, b, c));
return 0;
}
Merge sort는 정렬의 한 방법으로 분할 정복 기법을 사용하여 주어진 배열을 정렬하는 방법입니다. Merge sort는 아래와 같은 과정으로 배열을 정렬합니다.
주어진 배열이 충분히 작은 상태이거나 비교 가능한 상태가 될 때까지 반으로 나눔(divide)
나뉘어진 배열을 비교, 이때 두 배열은 단위 배열이거나 정렬된 상태의 배열이므로 가장 앞의 원소를 비교하며 새로운 배열에 정렬된 상태로 배치(conquer)
#include <bits/stdc++.h>
using namespace std;
void merge(vector<int>& seq, int l, int r) {
vector<int> tmp;
int mid = (l + r) / 2;
int f = l, s = mid + 1;
while (f <= mid && s <= r) {
if (seq[f] <= seq[s])
tmp.push_back(seq[f++]);
else
tmp.push_back(seq[s++]);
}
for (; f <= mid; ++f)
tmp.push_back(seq[f]);
for (; s <= r; ++s)
tmp.push_back(seq[s]);
for (int i = l; i <= r; ++i)
seq[i] = tmp[i - l];
}
void MergeSort(vector<int>& seq, int l, int r) {
if (l < r) {
int mid = (l + r) / 2;
MergeSort(seq, l, mid);
MergeSort(seq, mid + 1, r);
merge(seq, l, r);
}
}
int main() {
srand(time(0));
int n; scanf_s("%d", &n);
vector<int> seq(n);
for (int i = 0; i < n; ++i)
seq[i] = rand() % 100 + 1;
MergeSort(seq, 0, n - 1);
for (auto i : seq)
printf("%d ", i);
}
Decision problem은 결정 문제라는 뜻입니다. 어떤 조건이 주어지고 '조건에 맞는 값을 구하라' 등의 문제를 '어떤 값 A가 주어졌을 때 이 값은 조건에 맞는가' 식의 결정 문제로 바꿔서 해결할 수 있습니다. 또한 이때 A를 고르고 조건에 맞은 뒤 또 더 조건에 가까운 다른 A를 구하는 과정에서 Binary search를 사용하면 매우 빠르게 문제를 해결할 수 있습니다.
해당 문제는 주어진 값들을 양방향 그래프 형식으로 저장한 뒤, BFS와 binary search를 이용한 decision problem 방식으로 해결할 수 있습니다.
#include <bits/stdc++.h>
using namespace std;
struct EDGE {
int to, w;
};
int n, m, s, f;
vector<EDGE> graph[10001];
bool chk(int num) { // BFS사용
int check[10001] = { 0 };
queue<EDGE> q;
q.push({ s, (int)1e9 });
check[s] = 1;
while (q.size()) {
int frontTo = q.front().to;
int frontW = q.front().w;
q.pop();
if (frontTo == f && frontW >= num)
return 1;
for (int i = 0; i < graph[frontTo].size(); i++) {
int nt = graph[frontTo][i].to;
int nw = graph[frontTo][i].w;
if (!check[nt] && nw >= num) {
check[nt] = 1;
q.push({ nt, nw });
}
}
}
return 0;
}
int main() {
scanf_s("%d %d", &n, &m);
int l = 1e9;
int r = 0;
for (int i = 0; i < m; i++) {
int from, to, w;
scanf_s("%d %d %d", &from, &to, &w);
graph[from].push_back({ to, w });
graph[to].push_back({ from, w });
l = min(l, w);
r = max(r, w);
}
scanf_s("%d %d", &s, &f);
int ans = 0;
while (l <= r) { // Binary search를 이용한 decision problem 방식
int mid = (l + r) / 2;
if (chk(mid)) {
l = mid + 1;
ans = mid;
}
else
r = mid - 1;
}
printf("%d", ans);
return 0;
}