Problem link: https://www.acmicpc.net/problem/11689
그닥 어려운 문제는 아니었지만, 여러 개념들을 복기해내야해서 나름대로 애를 먹었다.
배울게 많아 좋은 문제라는 생각이 들어 조금은 공을 들여 기록을 남겨본다.
참고로, 이 문제는 Euler's phi 함수를 사용하면 매우 쉽게 풀 수 있다.
다만, 함수를 사용해서 풀다보면 배우고 지나가면 좋을 개념들을 쉽게 지나칠 수 있다.
따라서, 이 포스팅에서는 Euler's phi 함수를 사용하지 않고 풀이를 적어보려한다(글의 끝에 알게될테지만 원리는 같다).
문제를 풀기 위한 최초의 insight는 몇 번 손으로 풀어보면서 힌트를 얻을 수 있다.
중요한 직관은 GCD(n, k) = 1
인 k
의 갯수는 n
(전체 자연수의 갯수)에서 n
의 소인수의 배수들의 갯수를 빼준 것과 같다는 사실이다.
예를 들어, n == 45
인 경우 n
의 소인수는 factors={3, 5}
임을 알 수 있다.
n
이하 자연수들 중 3 또는 5의 배수의 수를 빼준다면 답은 곧 answer == 45 - 45/3 - 45/5 + 45/15 == 24
가 됨을 알 수 있다.
이제, 주어지는 n
의 소인수 리스트(i.e. factors
)를 구하는 방법을 생각해보자.
당연히, 에라토스테네스 체를 이용하면 쉽게 구할 수 있는데 n
의 범위가 10^12으로 꽤나 커서 단순한 방법을 사용하기에는 무리가 있다.
이 때 사용할 수 있는 한 가지 좋은 성질이 있는데, 이는 아래와 같다.
n
의 소인수는 모두 sqrt(n)
보다 작거나 같거나, 단 하나만이 sqrt(n)
보다 크다.n
이 sqrt(n)
보다 큰 2개 이상의 소인수를 갖는다고 하자.f0
, f1
라고 하자.n == f0 * f1 * ...
와 같이 표현할 수 있다는 이야기인데, f0
, f1
이 모두 sqrt(n)
보다 크다면 n < f0 * f1 * ...
이 되어 가정에 모순이다.본격적으로, 상술한 성질을 바탕으로 n
의 factors
를 구해보자.
n
의 factors
를 구하기 위해서는 우선 sqrt(n)
이하의 소수들을 에라토스테네스 체를 이용하여 구해놓자.n
의 소인수를 만날 때마다, 발견한 소인수를 factors
에 넣어주고, n
을 구한 소인수로 계속 나누어서 찾은 소인수 성분을 제거해주자.n
이 1이 아니라면 이 수가 sqrt(n)
을 초과하는 소인수가 될 것이다.div = n
for i in range(2, sqrt(n) + 1):
if div % i == 0 and is_prime[i]: # i가 div의 소인수라면,
factors.append(i) # i는 자명하게 n의 소인수이다.
while div % i == 0: # i를 더이상 소인수로 가지지 않을 때까지 나누자
div = div / i
if div != 1: # sqrt(n) 보다 큰 단 하나의 소인수가 존재하는 경우
factors.append(div)
factors
를 구한 뒤에는 n
에서 factor[0]
의 배수, factor[1]
의 배수, ...을 모두 빼주어야 한다.
공배수 문제가 있기 때문에, 여기서는 포함-배제 원리를 사용해주어야만 한다.
홀수개 집합은 더하고, 짝수개 집합은 뺀다로 기억하면 편리한 원리인데, 자세한 내용은 링크를 참조하면 된다.
내 풀이에서는 비트마스크를 활용해서 집합을 표현했다.
#include <iostream>
#include <cstdint>
#include <vector>
using namespace std;
const int kSqrtMaxN = 1000000;
int64_t N;
bool IS_COMP[kSqrtMaxN + 1];
void DoSieve(void)
{
for (int64_t i = 2; i <= kSqrtMaxN; ++i)
{
if (!IS_COMP[i])
{
for (int64_t j = i * i; j <= kSqrtMaxN; j += i)
{
IS_COMP[j] = true;
}
}
}
}
int64_t Solve(void)
{
vector<int64_t> factors;
int64_t n = N;
for (int64_t i = 2; i * i <= N; ++i)
{
if (n % i == 0 && !IS_COMP[i])
{
factors.emplace_back(i);
while (n % i == 0)
{
n /= i;
}
}
}
if (n != 1)
{
factors.emplace_back(n);
}
int64_t sum = 0;
for (int mask = 1; mask < (1 << factors.size()); ++mask)
{
int64_t lcm = 1;
int64_t number_of_sets = 0;
for (int bit = 0; (1 << bit) <= mask; ++bit)
{
if (mask & (1 << bit))
{
lcm *= factors[bit];
++number_of_sets;
}
}
if (number_of_sets % 2)
{
sum += N / lcm;
}
else
{
sum -= N / lcm;
}
}
return N - sum;
}
int main(void)
{
// For Faster IO
ios_base::sync_with_stdio(false);
cout.tie(nullptr);
cin.tie(nullptr);
// Read Input
cin >> N;
// Solve
DoSieve();
cout << Solve() << '\n';
return 0;
}