Notice
Recent Posts
Recent Comments
Link
승코딩당당당
[C++] 백준 11689: GCD(n, k) = 1 본문
문제
[C++] 백준 11689: GCD(n, k) = 1 GOLD 1
https://www.acmicpc.net/problem/11689

접근 방법
이 문제는 어떤 수 n에 대해, 1부터 n까지의 수 중에서 n과 서로소인 수의 개수를 구하는 문제다.
이는 곧 오일러 피 함수 φ(n)를 구하는 문제이고, 공식을 이용하면 다음처럼 쓸 수 있다.

여기서
- p∣n 은 “p가 n의 약수(소인수)”라는 뜻
- 곱은 “n의 서로 다른 소인수들”에 대해서만 한 번씩 해주면 된다.
즉,
- n을 소인수분해해서 서로 다른 소인수 p 들을 찾고
- ret에 ret = ret * (1 - 1 / p)를 순서대로 적용해 주면 최종적으로 ret이 φ(n)이 된다.
이 공식을 그대로 구현하면, n이 최대 범위일 때도 O(√n) 안에 해결할 수 있다.
상세 아이디어
1. n과 ret 역할 분리
코드에서 변수 두 개를 이렇게 둔다.
LL n; // 입력 받은 수, 소인수를 제거해 가며 줄여나가는 용도
LL ret = n; // 실제로 φ(n)을 계산해서 담아둘 결과 변수
- n은 소인수분해용 “재료”
→ p로 나눠지면 계속 나누면서 점점 작아진다. - ret은 오일러 피 함수 결과를 저장하는 변수
→ 소인수 p를 찾을 때마다
ret = ret - ret / p 로 값을 줄여나간다.
2. 소인수 p 찾기 + 오일러 피 공식 적용
반복문으로 작은 수부터 차례대로 나눠보며 n의 소인수를 찾아간다.
for (LL p = 2; p <= sqrt(n); p++)
{
if (n % p == 0) // p가 n을 나누면, p는 n의 소인수
{
ret = ret - (ret / p); // ret *= (1 - 1/p)와 같은 의미
while (n % p == 0) // 같은 소인수를 중복 처리하지 않기 위해
n /= p; // n에서 p를 모두 제거
}
}
여기서 중요한 포인트:
- ret = ret - ret / p
→ ret *= (1 - 1/p)를 정수 연산으로 바꾼 것
→ “p의 배수들은 φ(n) 후보에서 빠져야 한다”는 의미 (에라토스테네스의 체 원리와 유사하다.) - while (n % p == 0) n /= p;
→ n에서 소인수 p를 가능한 만큼 다 나눠서 제거
→ 같은 p를 다시 만나서 또 한 번 더 ret = ret - ret / p 를 적용하지 않도록 하는 역할
→ 오일러 피 공식은 “서로 다른 소인수”마다 한 번씩만 적용해야 하기 때문
3. 마지막 남은 소인수 처리
반복문이 끝난 뒤에도 n이 1보다 크면, 그 값 자체가 마지막 소인수(또는 소수)로 남아 있는 상태다.
예를 들어,
- 처음 n이 12라면
- p = 2, 3이 처리되고
- 마지막 n은 1이 된다.
- 처음 n이 17(소수)라면
- for문에서 어떤 p도 n을 나누지 못하고
- 마지막에 그대로 n = 17이 남아 있다.
즉, 반복문에서 제곱근까지만 탐색했기 때문에 1개의 소인수가 누락되는 케이스이다.
이런 경우를 처리하기 위해 다음 코드를 추가한다.
if (n > 1)
ret = ret - (ret / n);
이 한 줄로, 마지막으로 남은 소인수 n에 대해서도 ret *= (1 - 1/n) 을 적용해주는 셈이다.
구현 시 주의할 점
- 자료형은 반드시 long long 사용하기 (LL)
- 입력 n의 범위가 크기 때문에, int로는 오버플로우가 날 수 있다.
- n과 ret 역할을 헷갈리지 않기
- n → 소인수분해를 위해 계속 나눠서 줄이는 “작업용” 값
- ret → 오일러 피 함수의 결과를 담는 값
- while (n % p == 0) n /= p;에서 나누는 대상은 항상 n이다.
여기서 ret을 나누면 안 된다.
- 같은 소인수에 대해 한 번만 φ(n) 공식 적용하기
- while (n % p == 0)로 소인수 p를 n에서 완전히 제거한 뒤,
다시는 같은 p에 대해 ret = ret - ret / p가 실행되지 않도록 한다. - 그렇지 않으면 (1 - 1/p)를 여러 번 곱하는 꼴이 되어 잘못된 결과가 나온다.
- while (n % p == 0)로 소인수 p를 n에서 완전히 제거한 뒤,
- 루프 조건에서 sqrt(n) 사용 시 주의
- 여기서는 for (LL p = 2; p <= sqrt(n); p++)처럼 썼는데,
반복문 안에서 n이 줄어들기 때문에 sqrt(n) 값도 함께 줄어든다. - 이는 오히려 연산량을 줄여주는 효과가 있고,
마지막에 if (n > 1)에서 남은 소인수를 처리하므로 소인수를 놓치는 일은 없다.
- 여기서는 for (LL p = 2; p <= sqrt(n); p++)처럼 썼는데,
코드
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
LL n = 0;
cin >> n;
LL ret = n;
for (LL p = 2; p <= sqrt(n); p++)
{
if (n % p == 0) // p가 소인수라면
{
ret = ret - (ret / p);
while (n % p == 0) // 해당 소인수 지우기
n /= p;
}
}
if (n > 1)
ret = ret - (ret / n);
cout << ret;
}'PS > BOJ' 카테고리의 다른 글
| [C++] 백준 1850: 최소공약수 (0) | 2026.03.02 |
|---|---|
| [C++] 백준 1934: 최소공배수 (0) | 2026.03.02 |
| [C++] 백준 1016: 제곱 ㄴㄴ 수 (0) | 2026.03.02 |
| [C++] 백준 1620: 나는야 포켓몬 마스터 이다솜 (0) | 2026.03.01 |
| [C++] 백준 1991: 트리 순회 (0) | 2026.02.27 |