조화 수열
조화 수열은 다음과 같이 전개되는 꼴의 식을 의미한다.
[ N 1 ] , [ N 2 ] , [ N 3 ] ⋯ [ N n ]
\Bigl[\frac N1 \Bigr],\ \Bigl[\frac N2 \Bigr],\ \Bigl[\frac N3\Bigr]\ \cdots \ \Bigl[\frac Nn\Bigr]
[ 1 N ] , [ 2 N ] , [ 3 N ] ⋯ [ n N ]
이 수열은 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 = k i=k i = k 라면 안쪽의 for
문 반복 횟수는 대략 [ N k ] \left[\dfrac Nk\right] [ k N ] 번 이다.
물론 에라토스테네스의 체는 이미 소수가 아닌 수일 경우 반복문을 반복하지 않기 때문에 시간복잡도적인 측면에서 큰 상관이 없지만 그래도 이러한 느낌을 가져간다고 할 수 있다.
시간복잡도
∑ i = 1 N [ N i ] \quad \displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] i = 1 ∑ N [ i N ] 를 구한다고 해보자. 이것의 시간복잡도는 얼마일까?
naive하게 구하면 O ( N ) O(N) O ( N ) 이지만, 여기서 중요한 테크닉 이 나오는데, i i i 에 대해 하나하나 구해주는 것이 아닌, [ N i ] \left[\dfrac Ni\right] [ i N ] 가 동일한 값을 가진 구간들의 길이를 세는것이다.
[ N i ] = k \left[\dfrac Ni\right]=k [ i N ] = k 의 값을 가지는 가장 큰 실수 i i i 는 N k \dfrac Nk k N 이다.
그러면 이 구간에서의 가장 큰 정수는 [ N k ] \left[\dfrac Nk\right] [ k N ] 이다.
그러므로 [ N i ] = [ N k ] \left[\dfrac Ni \right]=\left[\dfrac Nk \right] [ i N ] = [ k N ] 가되기 위한 최대 정수 i i i 는 [ N [ N k ] ] \color{salmon} \biggl[ \dfrac {N}{\left[ \frac Nk \right]} \biggr] [ [ k N ] N ] 이다.
코드로 보면,
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 ( 1 ) 에 구할 수 있으므로 구간들의 개수가 결국 시간 복잡도와 연관이 있다는 뜻이다.
결론적으로 이 구간의 개수는 O ( 2 N ) \color{salmon} O(2\sqrt N) O ( 2 N ) 인데, 그 이유를 살펴보자.
i < N i<\sqrt N i < N 일 때,
어차피 i i i 가 N \sqrt N N 개보다 적으므로 O ( N ) O(\sqrt N) O ( N ) 이다.
i ≥ N i \geq \sqrt N i ≥ N 일 때
[ N i ] \left[ \dfrac Ni \right] [ i N ] 의 값 자체가 조건에 의하여 N \sqrt N N 개를 넘을 수 없으므로 O ( N ) O(\sqrt N) O ( N ) 이다.
따라서 O ( 2 N ) O(2\sqrt N) O ( 2 N ) 이다.
약수의 개수, 약수의 합 세기
이건 정수론에서 τ \tau τ 와 σ \sigma σ 를 의미하는 것이므로 특정 숫자에 대해서만 구한다면 소인수분해하여 공식을 활용해줄 수도 있다.
펼치기
n = p 1 k 1 + p 2 k 2 + ⋯ + p m k m n=p_1^{k_1}+p_2^{k_2}+\cdots+p_m^{k_m} n = p 1 k 1 + p 2 k 2 + ⋯ + p m k m
τ n = ∏ i = 1 m ( k i + 1 ) \tau_n=\displaystyle \prod_{i=1}^m (k_i+1) τ n = i = 1 ∏ m ( k i + 1 )
σ n = ∏ i = 1 m p i k i + 1 − 1 p i − 1 ( 등비수열공식 ) \sigma_n=\displaystyle \prod_{i=1}^m \dfrac{p_i^{k_{i+1}}-1}{p_i-1}~~(등비수열 공식) σ n = i = 1 ∏ m p i − 1 p i k i + 1 − 1 ( 등비수열공식 )
그러나 특정 구간의 모든 수들에 대해서의 τ \tau τ 와 σ \sigma σ 의 합은 조화 수열의 합으로 구할 수 있다.
이건 수들의 약수의 개수를 모두 세서 더해주는 방식이 아닌 범위 내에서의 배수의 개수를 세서 모두 더해주는 방법 으로 치환하여 구할 수 있다.
어떤 수 k k k 의 n n n 이하에서의 배수의 개수는 [ n k ] \left[\dfrac nk\right] [ k n ] 인데,
[ l , r ] [l, r] [ l , r ] 구간에서의 배수의 개수는 [ r k ] − [ l − 1 k ] \Bigl[ \dfrac rk \Bigr] - \Bigl[ \dfrac {l-1}k \Bigr] [ k r ] − [ k l − 1 ] 이다.
이는 결국 ∑ i = 1 N [ N i ] \displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] i = 1 ∑ N [ i N ] 이기 때문이다.
즉 이 식이 의미하는건 N N N 이하에서의 수들의 약수의 개수의 총합이다.
약수의 합(=배수의 합)도 다르지 않다.
∑ i = 1 N i [ N i ] \quad \displaystyle \sum_{i=1}^N i\left[ \frac Ni \right] i = 1 ∑ N i [ i N ] 를 계산해주면 된다.
이러한 꼴은 지금처럼 ∑ i = 1 N [ N i ] \displaystyle \sum_{i=1}^N \left[ \dfrac Ni \right] i = 1 ∑ N [ i N ] 꼴과 다르게 ∑ i = 1 N f ( x ) [ N i ] \displaystyle \sum_{i=1}^N f(x)\left[ \dfrac Ni \right] i = 1 ∑ N f ( x ) [ i N ] 가 된 꼴이라고 볼 수 있는데, 이 경우 [ N i ] = k \left[ \dfrac Ni \right]=k [ i N ] = k 인 구간 [ l , r ] [l, r] [ l , r ] 에서 ∑ i = l r f ( i ) \displaystyle \sum_{i=l}^r f(i) i = l ∑ r f ( i ) 를 [ N i ] \left[ \dfrac Ni \right] [ i N ] 에 곱해주는 것으로 O ( N ) O(N) O ( N ) 이 아닌 O ( N ) \color{salmon} O(\sqrt N) O ( N ) 에 문제를 해결할 수 있도록 해준다.
물론 여기에 ∑ i = l r f ( i ) \displaystyle \sum_{i=l}^r f(i) i = l ∑ r f ( i ) 를 구하는 시간복잡도는 별도로 곱해진다.
배수를 구하는 것은 ∑ i = 1 N i [ N i ] \quad \displaystyle \sum_{i=1}^N i\left[ \frac Ni \right] i = 1 ∑ N i [ i N ] 를 구하는 것이라고 했고, ∑ i = l r i \displaystyle \sum_{i=l}^r i i = l ∑ r i 는 r ( r + 1 ) 2 − l ( l − 1 ) 2 = ( r − l + 1 ) ⋅ ( r + l ) 2 \dfrac {r(r+1)}2-\dfrac{l(l-1)}2=\dfrac {(r-l+1)\cdot(r+l)}2 2 r ( r + 1 ) − 2 l ( l − 1 ) = 2 ( r − l + 1 ) ⋅ ( r + l ) 이므로 O ( 1 ) O(1) O ( 1 ) 에 구해진다.
연습 문제들
BOJ 15897 - 잘못 구현한 에라토스테네스의 체
코드를 보면 j = i j=i j = i 로 초기화 되는것이 아니라서 온전히 조화수열의 합이 정답이 아니기 때문에 일단 j = 1 j=1 j = 1 일 때의 모든 수들의 정답인 N N N 개를 정답에 더해주고, N ≔ N − 1 N \coloneqq N-1 N : = N − 1 로 바꿔준다음 문제를 풀면 된다.
마치 j = 0 j=0 j = 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
References
ahgus89님 블로그
Comments