조화 수열Permalink

조화 수열은 다음과 같이 전개되는 꼴의 식을 의미한다.

[N1], [N2], [N3]  [Nn] \Bigl[\frac N1 \Bigr],\ \Bigl[\frac N2 \Bigr],\ \Bigl[\frac N3\Bigr]\ \cdots \ \Bigl[\frac Nn\Bigr]

이 수열은 PS에서 꽤나 자주 등장하는데, 이걸로 직접 문제를 풀거나 시간복잡도를 분석하는 등 써먹을 것이 많기 때문이다.

예를 들어, 에라토스테네스의 체를 보자.

const int MAX = 1e2;
vector<bool> is_prime(MAX + 1, true);
vector<int> primes;
for (int i = 2; i <= MAX; i++) {
   if (!is_prime[i]) continue;
   primes.push_back(i);
   for (int j = i + i; j <= MAX; j += i)
      is_prime[j] = false;
}

이런 코드가 있다고 할 때, i=ki=k 라면 안쪽의 for문 반복 횟수는 대략 [Nk]\left[\dfrac Nk\right]번 이다.

물론 에라토스테네스의 체는 이미 소수가 아닌 수일 경우 반복문을 반복하지 않기 때문에 시간복잡도적인 측면에서 큰 상관이 없지만 그래도 이러한 느낌을 가져간다고 할 수 있다.

시간복잡도Permalink

i=1N[Ni]\quad \displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] 를 구한다고 해보자. 이것의 시간복잡도는 얼마일까?

naive하게 구하면 O(N)O(N) 이지만, 여기서 중요한 테크닉이 나오는데, ii에 대해 하나하나 구해주는 것이 아닌, [Ni]\left[\dfrac Ni\right]가 동일한 값을 가진 구간들의 길이를 세는것이다.

[Ni]=k\left[\dfrac Ni\right]=k 의 값을 가지는 가장 큰 실수 iiNk\dfrac Nk 이다.

그러면 이 구간에서의 가장 큰 정수는 [Nk]\left[\dfrac Nk\right]이다.

그러므로 [Ni]=[Nk]\left[\dfrac Ni \right]=\left[\dfrac Nk \right] 가되기 위한 최대 정수 ii[N[Nk]]\color{salmon} \biggl[ \dfrac {N}{\left[ \frac Nk \right]} \biggr] 이다.

코드로 보면,

int N = 100;  
  
for (int l = 1, r; l <= N; l = r + 1) {  
   int k = N / l;  
   r = N / k;  
   debug(l, r, k);  
}
1, 1, 100
2, 2, 50
3, 3, 33
4, 4, 25
5, 5, 20
6, 6, 16
7, 7, 14
8, 8, 12
9, 9, 11
10, 10, 10
11, 11, 9
12, 12, 8
13, 14, 7
15, 16, 6
17, 20, 5
21, 25, 4
26, 33, 3
34, 50, 2
51, 100, 1

처럼 나온다.

구간을 O(1)O(1)에 구할 수 있으므로 구간들의 개수가 결국 시간 복잡도와 연관이 있다는 뜻이다.

결론적으로 이 구간의 개수는 O(2N)\color{salmon} O(2\sqrt N) 인데, 그 이유를 살펴보자.

  1. i<Ni<\sqrt N 일 때,

어차피 iiN\sqrt N 개보다 적으므로 O(N)O(\sqrt N) 이다.

  1. iNi \geq \sqrt N 일 때

[Ni]\left[ \dfrac Ni \right] 의 값 자체가 조건에 의하여 N\sqrt N 개를 넘을 수 없으므로 O(N)O(\sqrt N) 이다.

따라서 O(2N)O(2\sqrt N) 이다.

약수의 개수, 약수의 합 세기Permalink

이건 정수론에서 τ\tauσ\sigma 를 의미하는 것이므로 특정 숫자에 대해서만 구한다면 소인수분해하여 공식을 활용해줄 수도 있다.

n=p1k1+p2k2++pmkmn=p_1^{k_1}+p_2^{k_2}+\cdots+p_m^{k_m}

τn=i=1m(ki+1)\tau_n=\displaystyle \prod_{i=1}^m (k_i+1)

σn=i=1mpiki+11pi1  (등비수열공식)\sigma_n=\displaystyle \prod_{i=1}^m \dfrac{p_i^{k_{i+1}}-1}{p_i-1}~~(등비수열 공식)

그러나 특정 구간의 모든 수들에 대해서의 τ\tauσ\sigma 의 합은 조화 수열의 합으로 구할 수 있다.

이건 수들의 약수의 개수를 모두 세서 더해주는 방식이 아닌 범위 내에서의 배수의 개수를 세서 모두 더해주는 방법으로 치환하여 구할 수 있다.

어떤 수 kknn 이하에서의 배수의 개수는 [nk]\left[\dfrac nk\right] 인데,

[l,r][l, r] 구간에서의 배수의 개수는 [rk][l1k]\Bigl[ \dfrac rk \Bigr] - \Bigl[ \dfrac {l-1}k \Bigr] 이다.

이는 결국 i=1N[Ni]\displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] 이기 때문이다.

즉 이 식이 의미하는건 NN 이하에서의 수들의 약수의 개수의 총합이다.

약수의 합(=배수의 합)도 다르지 않다.

i=1Ni[Ni]\quad \displaystyle \sum_{i=1}^N i\left[ \frac Ni \right] 를 계산해주면 된다.

이러한 꼴은 지금처럼 i=1N[Ni]\displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] 꼴과 다르게 i=1Nf(x)[Ni]\displaystyle \sum_{i=1}^N f(x)\left[ \dfrac Ni \right] 가 된 꼴이라고 볼 수 있는데, 이 경우 [Ni]=k\left[ \dfrac Ni \right]=k 인 구간 [l,r][l, r]에서 i=lrf(i)\displaystyle \sum_{i=l}^r f(i)[Ni]\left[ \dfrac Ni \right] 에 곱해주는 것으로 O(N)O(N) 이 아닌 O(N)\color{salmon} O(\sqrt N) 에 문제를 해결할 수 있도록 해준다.

물론 여기에 i=lrf(i)\displaystyle \sum_{i=l}^r f(i) 를 구하는 시간복잡도는 별도로 곱해진다.

배수를 구하는 것은 i=1Ni[Ni]\quad \displaystyle \sum_{i=1}^N i\left[ \frac Ni \right] 를 구하는 것이라고 했고, i=lri\displaystyle \sum_{i=l}^r ir(r+1)2l(l1)2=(rl+1)(r+l)2\dfrac {r(r+1)}2-\dfrac{l(l-1)}2=\dfrac {(r-l+1)\cdot(r+l)}2 이므로 O(1)O(1)에 구해진다.

연습 문제들Permalink

BOJ 15897 - 잘못 구현한 에라토스테네스의 체

코드를 보면 j=ij=i 로 초기화 되는것이 아니라서 온전히 조화수열의 합이 정답이 아니기 때문에 일단 j=1j=1 일 때의 모든 수들의 정답인 NN 개를 정답에 더해주고, NN1N \coloneqq N-1 로 바꿔준다음 문제를 풀면 된다.

마치 j=0j=0 부터 시작시키는 것으로 바꾸어 주었다고 생각하면 된다.

void solve() {  
   int N;  
   cin >> N;  
   ll ans = N;  
   N--;  
   for (int l = 1, r; l <= N; l = r + 1) {  
      int k = N / l;  
      r = N / k;  
      ans += 1ll * (r - l + 1) * k;  
   }  
   cout << ans;  
}

BOJ 17417 - Optimization is Freaky Fun


ReferencesPermalink

ahgus89님 블로그

Comments