https://www.acmicpc.net/problem/34620
분모가 2의 제곱수인 기약분수를 2개의 연산으로 표현할 수 있는 가장 짧은 문자열을 구하는 문제.
진법 변환의 원리에 대한 사전지식이 있어야 한다.
우선 분모가 2의 제곱수가 아닌 숫자는 배제시켜야 하기 때문에 and 연산자를 통해 걸러내었다.

어떤 숫자가 2의 제곱수일 때 그 숫자에서 1을 뺀 값과 곱 연산을 했을 때는 0이 나온다.
ex ) 8 --> 1000, 7 ---> 0111
8 & 7 ----> 0
그 후에는 가장 짧은 문자열을 찾는 과정이다.
숫자를 쌓는 방식은 다음과 같다.
3 / 8은 1 / 4 + 1 / 8 이다.
이는 다시
(1 / 2 + 1 / 4) / 2
(1 + 1 / 2) / 2 / 2
로 표현이 가능하며 이때의 최소 길이 문자열은
G K G K K 이다.
원리가 조금 어렵게 느껴진다.
가장 초반에 더한 1이 그 숫자의 가장 작은 2의 제곱수 분모로 들어가는 것이다.
하지만 이렇게 어렵게 생각할 문제는 아니였다.
이 문제에서 주어진 연산은 단 2가지.
1. 숫자에 1을 더한다
2. 숫자를 2로 나눈다.
이다.
주어진 기약 분수 또한 그러한 과정을 거쳐왔을 것임을 유추할 수 있다.
또한 주어진 숫자는 반드시 1보다 작기 때문에 1을 더한 연산 후에는 반드시 2로 나눈 연산이 나타날 것 또한 예상할 수 있다.
그렇다면 숫자에 대한 연산을 거꾸로 따라가면 되는 것이다.
3 / 8
= 3 / 4 * 1 / 2
= 3 / 2 * 1 / 2 * 1 / 2
= (1 + 1 / 2) * 1 / 2 * 1 / 2
= ((0 + 1) * 1 / 2 + 1) * 1 / 2 * 1 / 2
와 같은 형태를 따르는 것이다.
원래의 연산은 +1과 나누기 2였다면 이번에는 해당 숫자에서 -1과 곱하기 2 만으로 0에 도달하는 경로를 찾으면 되는 것이다.
두 연산을 하는 시점은 해당 숫자가 1보다 작은 경우에는 * 2, 1보다 큰 경우에는 -1을 해주면 되는 것이다.
연산순서를 구하기 위해서는 역산한 순서를 뒤집으면 된다.
이 문제의 그리디 전략은 사전순으로 가장 짧은(더하기 연산을 먼저 하는) 연산은
해당 숫자에서 0으로 도달하는 경로와 동일하다는 것이였다.
앞으로도 처음에서 끝으로 도달하는 것이 험난해보인다면 역산을 해보는 시도를 해보자.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int a, b;
vector<char> ans;
cin >> a >> b;
if(b & (b-1)) {
cout << "-1";
return 0;
}
while(a) {
if(a >= b) {
a -= b;
ans.push_back('G');
} else {
b >>= 1;
ans.push_back('K');
}
}
reverse(ans.begin(), ans.end());
for(char a : ans) {
cout << a;
}
}
/*
3 / 8
1/ 4 + 1 / 8
(1 / 2 + 1 / 4) / 2
(1 + 1 / 2) / 2 / 2
1 / 2 + 1
1 / 8
K K K G K G
3 */
'알고리즘' 카테고리의 다른 글
| Boj 1700 c++멀티탭 스케줄링 (0) | 2025.11.15 |
|---|---|
| Boj 14891 c++ 톱니바퀴 (0) | 2025.11.14 |
| 가장 긴 증가하는 부분 수열 문제 모음 (0) | 2025.10.26 |
| Boj 14267 c++ 회사문화 (0) | 2025.10.25 |
| Boj 2239 c++ 스도쿠 (0) | 2025.10.25 |