$$\newcommand{\Z}{\mathbb{Z}} \newcommand{\R}{\mathbb{R}} \newcommand{\Q}{\mathbb{Q}} \newcommand{\N}{\mathbb{N}}\newcommand{\C}{\mathbb{C}} \newcommand{\oiv}[1]{\left] #1 \right[} \newcommand{\civ}[1]{\left[ #1 \right]} \newcommand{\ad}[1]{\text{ad}(#1)} \newcommand{\acc}[1]{\text{acc}(#1)} \newcommand{\Setcond}[2]{ \left\{\, #1 \mid #2 \, \right\}} \newcommand{\Set}[1]{ \left\{ #1 \right\}} \newcommand{\abs}[1]{ \left\lvert #1 \right\rvert}\newcommand{\norm}[1]{ \left\| #1 \right\|}\newcommand{\prt}{\mathcal{P}}\newcommand{\st}{\text{ such that }}\newcommand{\for}{\text{ for }} \newcommand{\cl}[1]{\text{cl}(#1)}\newcommand{\oiv}[1]{\left] #1 \right[}\newcommand{\interior}[1]{\text{int}(#1)}$$

BOJ 1557 제곱 ㄴㄴ / BOJ 8464 Non-Squarefree Numbers::::Gratus' Blog

BOJ 1557 제곱 ㄴㄴ / BOJ 8464 Non-Squarefree Numbers

알고리즘 문제풀이/BOJ 2020. 1. 4. 02:15

난이도 : Solved.ac 기준 다이아 5

사실상 거의 같은 문제인 1557과 8464는 둘 모두 Square Free Number의 개수에 관한 문제이다. 

풀이는 1557을 기준으로 설명하고, 마지막에 8464에 적용하는 것은 간단하다 :)

 

 

1. 문제 설명

대략 $10^{10}$ 정도 되는 $n$이 주어지고 (1557은 $10^9$), 이때 $n$번째 Square Free 인 수 구하기 / Square Free가 아닌 수 구하기. 결국은 $n$ 번째 Square free 한 수를 구하는 문제라고 생각하고 풀어 보자.

 

 

2. 풀이

$n$번째를 구하는 데 $O(n)$ 시간 미만을 쓰는 것은 어려워 보이므로 다음과 같이 생각해 보자.

$S_n$ 을 $n$보다 작거나 같은 Square Free Number (귀찮으니까 SFN 이라고 하자) 의 개수라고 정의하자. 이때, $S_n$ 은 자명하게 단조증가하므로 이 함수에 대해 Parametric Search 같은 방법을 이용해서 문제를 해결할 수 있다. 

$n$번째 SFN이 대략 어느 정도일지 예측해야 양쪽 바운드를 잡을 수 있는데, 뭔가 많아 보이니 대충 넉넉하게 $10^{12}$ 정도 잡으면 충분해 보이고, 놀랍게도 $S_n$ 이 대략 $\frac{6}{\pi^2} n$ 임이 알려져 있으므로 사실은 이보다 훨씬 타이트하게 잡아도 된다.

 

아무튼, 이 시점에서 우리가 생각하는 시간 복잡도는 

$$T(n)\log 10^{12}$$ 니까, 대충 $T(n)$ 부분이 $n = 10^{10}$에 대해 몇백만 이내로 들어와 줘야 한다. 

즉, 이 문제는 다시 주어진 수보다 작은 SFN의 개수를 세는 문제로 바뀌며, 이를 포함 배제의 원리를 이용하여 기술하면 다음과 같이 쓸 수 있다.

 

$$S_n = n - \sum_{p_1^2 \leq n} \left[ \frac{n}{p_1^2} \right] + \sum_{p_1^2 p_2^2 \leq n} \left[ \frac{n}{p_1^2p_2^2} \right] - \sum_{p_1^2 p_2^2 p_3^2\leq n} \left[ \frac{n}{p_1^2p_2^2p_3^2} \right] \cdots$$

 

이 식을 잘 관찰해 보면, $\sqrt n$ 까지의 수들 중, 소수 0개가 포함된 수 (1) 은 더하고, 소수 1개로 구성된 수 (소수들) 은 빼고, 소수 2개의 곱인 수들은 더하고... 의 형태임을 알 수 있으며, 이를 나타내는 함수는 다시 뫼비우스 함수 $\mu(x)$ 이다. 보다 정확히 말하자면, 소수의 제곱 이상이 있는 수는 합에서 고려하지 않으므로 모두 버리고, 그외에는 $t$ 가 홀수개의 소수로 구성된 경우 $[n / t^2]$ 을 빼고, 짝수개인 경우 더하는 것임을 알 수 있으며 이를 짧게 쓰면,

$$S_n = \sum_{i \leq \sqrt n} \mu(i) \left[ \frac{n}{i^2} \right]$$ 이 식을 계산하면 된다는 사실을 알 수 있다.

넉넉하게 $10^6$ 까지의 Mobius function의 값을 미리 계산해 놓는다면, 이제 $S_n$ 을 $O(\sqrt{n})$ 시간에 계산할 수 있다. 뫼비우스 함수의 값은 $x$까지 구하는데 $x \log \log x$ 정도 시간에 구할 수 있으며, 시간 복잡도를 보면 눈치챌 수 있겠지만 에라토스테네스의 체와 매우 비슷한 방법을 이용하여 구한다. 

 

당연하게도, $S_n$ 대신 $S'_n$ 을 $n$까지의 SFN이 아닌 수들의 개수로 정의하면 그것도 $O(\sqrt n)$ 시간에 똑같이 구해서 똑같이 작성할 수 있다.

 

3. 코드

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define all(x) ((x).begin()),((x).end())
using pii = pair <int, int>;
#define int ll

int mobius[1010100];
int count_squarefree(int N)
{
	int ans = 0;
	for (int i = 1; i*i<=N; i++)
		ans += (mobius[i]*(N/(i*i)));
	return ans;
}

int32_t main()
{
	for (int i = 1; i <= 1000000; i++)
		mobius[i] = 1;
	for (int i = 2; i*i <= 1000000; i++)
	{
		if (mobius[i] == 1)
		{
			for (int j = i; j <= 1000000; j += i)
				mobius[j] *= -i;
			for (int j = i * i; j <= 1000000; j += i * i)
				mobius[j] = 0;
		}
	}
	for (int i = 2; i <= 1000000; i++)
	{
		if (mobius[i] == i)
			mobius[i] = 1;
		else if (mobius[i] == -i)
			mobius[i] = -1;
		else if (mobius[i] < 0)
			mobius[i] = 1;
		else if (mobius[i] > 0)
			mobius[i] = -1;
	}
	int k;
	cin >> k;
	int lo = 0, hi = 1e12;
	while (lo + 1 < hi)
	{
		int mid = (lo + hi) / 2;
		if (count_squarefree(mid)<k)
			lo = mid;
		else hi = mid;
	}
	cout << hi << '\n';
}

'알고리즘 문제풀이 > BOJ' 카테고리의 다른 글

BOJ 13728 행렬식과 GCD  (3) 2020.01.20
BOJ 17840 피보나치 음악  (0) 2020.01.09
BOJ 2465 줄 세우기  (0) 2019.10.31
BOJ 17410 수열과 쿼리 1.5  (0) 2019.10.30
BOJ 13505 두 수 XOR  (0) 2019.09.21
admin