BOJ 1557 제곱 ㄴㄴ / BOJ 8464 Non-Squarefree Numbers
알고리즘 문제풀이/BOJ 2020. 1. 4. 02:15난이도 : Solved.ac 기준 다이아 5
사실상 거의 같은 문제인 1557과 8464는 둘 모두 Square Free Number의 개수에 관한 문제이다.
풀이는 1557을 기준으로 설명하고, 마지막에 8464에 적용하는 것은 간단하다 :)
1. 문제 설명
대략 1010 정도 되는 n이 주어지고 (1557은 109), 이때 n번째 Square Free 인 수 구하기 / Square Free가 아닌 수 구하기. 결국은 n 번째 Square free 한 수를 구하는 문제라고 생각하고 풀어 보자.
2. 풀이
n번째를 구하는 데 O(n) 시간 미만을 쓰는 것은 어려워 보이므로 다음과 같이 생각해 보자.
Sn 을 n보다 작거나 같은 Square Free Number (귀찮으니까 SFN 이라고 하자) 의 개수라고 정의하자. 이때, Sn 은 자명하게 단조증가하므로 이 함수에 대해 Parametric Search 같은 방법을 이용해서 문제를 해결할 수 있다.
n번째 SFN이 대략 어느 정도일지 예측해야 양쪽 바운드를 잡을 수 있는데, 뭔가 많아 보이니 대충 넉넉하게 1012 정도 잡으면 충분해 보이고, 놀랍게도 Sn 이 대략 6π2n 임이 알려져 있으므로 사실은 이보다 훨씬 타이트하게 잡아도 된다.
아무튼, 이 시점에서 우리가 생각하는 시간 복잡도는
T(n)log1012 니까, 대충 T(n) 부분이 n=1010에 대해 몇백만 이내로 들어와 줘야 한다.
즉, 이 문제는 다시 주어진 수보다 작은 SFN의 개수를 세는 문제로 바뀌며, 이를 포함 배제의 원리를 이용하여 기술하면 다음과 같이 쓸 수 있다.
Sn=n−∑p21≤n[np21]+∑p21p22≤n[np21p22]−∑p21p22p23≤n[np21p22p23]⋯
이 식을 잘 관찰해 보면, √n 까지의 수들 중, 소수 0개가 포함된 수 (1) 은 더하고, 소수 1개로 구성된 수 (소수들) 은 빼고, 소수 2개의 곱인 수들은 더하고... 의 형태임을 알 수 있으며, 이를 나타내는 함수는 다시 뫼비우스 함수 μ(x) 이다. 보다 정확히 말하자면, 소수의 제곱 이상이 있는 수는 합에서 고려하지 않으므로 모두 버리고, 그외에는 t 가 홀수개의 소수로 구성된 경우 [n/t2] 을 빼고, 짝수개인 경우 더하는 것임을 알 수 있으며 이를 짧게 쓰면,
Sn=∑i≤√nμ(i)[ni2] 이 식을 계산하면 된다는 사실을 알 수 있다.
넉넉하게 106 까지의 Mobius function의 값을 미리 계산해 놓는다면, 이제 Sn 을 O(√n) 시간에 계산할 수 있다. 뫼비우스 함수의 값은 x까지 구하는데 xloglogx 정도 시간에 구할 수 있으며, 시간 복잡도를 보면 눈치챌 수 있겠지만 에라토스테네스의 체와 매우 비슷한 방법을 이용하여 구한다.
당연하게도, Sn 대신 S′n 을 n까지의 SFN이 아닌 수들의 개수로 정의하면 그것도 O(√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 |