약수를 세는 것보다 배수를 세는 것이 더 쉽다(홀수인 약수의 합, 유일한 소인수의 개수를 구하는 방법)
SW Expert Academy
SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!
swexpertacademy.com
어떤 정수 x의 약수 중 홀수인 약수의 합을 f(x)라고 할때, L,R이 주어지면 L이상 R이하의 모든 x에 대해 f(x)의 합을 구하는 문제
단순한 방법으로는 소인수분해를 해서 홀수인 소인수들의 곱으로 약수의 합을 구하면 된다.
https://deepdata.tistory.com/588
약수의 합과 약수의 개수 공식 익히기
1. 약수의 개수 자연수 n의 소인수분해가 $$n = p_{1}^{x_{1}}p_{2}^{x_{2}}...p_{k}^{x_{k}}$$라고 한다면, n의 양의 약수의 개수는 $$d(n) = (x_{1}+1)(x_{2}+1)...(x_{k}+1)$$ n을 소인수분해하여, 소인수들의 지수 + 1의
deepdata.tistory.com
여기서 핵심은 짝수 * 홀수 = 짝수, 짝수 * 짝수 = 짝수, 홀수 * 홀수 = 홀수니까 소인수를 홀수만 남기면 홀수인 약수만 얻을 수 있다.
또한 모든 소수중 유일하게 2만 짝수이다.
그래서 1부터 1000000까지 순회하면서 먼저 2로 나누어 떨어지지 않을때까지 나눈다.
나누고 나서, 이를 $O(\sqrt{n})$ 소인수분해를 한다.
소인수의 개수를 알면 등비수열의 합 공식을 곱해나가면 약수의 합을 구할 수 있다
#정수 i의 홀수인 약수의 합
n = 10**6
odd_sigma = [0]*(n+1)
for i in range(1,n+1):
k = i
#2로 나누어 떨어지지 않을때까지 나누면 홀수인 소인수만 남는다
while i % 2 == 0:
i //= 2
value = 1
#소인수분해를 하면서 각 소인수의 개수를 구한다
for j in range(3,int(i**(1/2))+1):
if i % j == 0:
count = 0
while i % j == 0:
i //= j
count += 1
#등비수열의 합으로 약수의 합에 누적곱을 해주면...
value *= (j**(count+1) - 1)//(j-1)
if i > 1: #i 자체가 소인수인경우
value *= (1+i)
odd_sigma[k] = value
$O(N\sqrt{N})$이라 N이 $10^{6}$이라 매우 느리다.. 제한시간이 14초라 8초정도로 통과하긴 한데
https://deepdata.tistory.com/893
1부터 n까지 모든 자연수들 각각의 약수의 누적합을 구하는 방법들(double counting, harmonic lemma, dynami
1. 문제 2247번: 실질적 약수 (acmicpc.net) 2247번: 실질적 약수 첫째 줄에 CSOD(n)을 1,000,000으로 나눈 나머지를 출력한다. www.acmicpc.net 1부터 n까지 각각 약수들 중 1과 자기 자신을 제외한 약수들의 합들
deepdata.tistory.com
https://deepdata.tistory.com/807
약수의 합 아주 빠르게 찾기 - 더블 카운팅(배수를 이용해 약수를 찾기), smallest prime factor
1. 문제 17427번: 약수의 합 2 (acmicpc.net) 17427번: 약수의 합 2 두 자연수 A와 B가 있을 때, A = BC를 만족하는 자연수 C를 A의 약수라고 한다. 예를 들어, 2의 약수는 1, 2가 있고, 24의 약수는 1, 2, 3, 4, 6, 8,
deepdata.tistory.com
"약수의 개수를 세는것보다, 배수를 이용하는게 더 쉽다"
에라토스테네스의 체에서 쓰던 것처럼 약수를 찾는것보다 배수를 찾는 방식으로 한다면?
루트 시간을 로그 시간으로 바꿀 수 있다
for i in range(2,int(n**(1/2))+1):
if result[i] == 1:
for j in range(i*i,n+1,i):
result[j] = 0
배열 odd_sigma[i]는 정수 i의 홀수인 약수의 합이다.
"1,3,5,7,...의 모든 홀수 i에 대하여 i의 배수는 i를 약수로 가진다"
그래서 1부터 2씩 증가시켜가며 모든 홀수 i에 대하여 i의 배수는 i부터 i씩 증가시켜가며 얻을 수 있으니까...
n = 10**6
odd_sigma = [0]*(n+1)
for i in range(1,n+1,2):
for j in range(i,n+1,i):
odd_sigma[j] += i
이렇게 하면 8초 걸리는 코드를 0.4초로 단축시킬 수 있다
#정수 i의 홀수인 약수의 합
#i의 모든 배수는 i를 약수로 가진다.
n = 10**6
odd_sigma = [0]*(n+1)
#홀수 i에 대하여...
for i in range(1,n+1,2):
#i의 배수 j는 i를 약수로 가지므로.. 이를 누적합시킨다.
for j in range(i,n+1,i):
odd_sigma[j] += i
#구간 [L,R]의 누적합을 구해야하므로 prefix sum
for i in range(1,n+1):
odd_sigma[i] += odd_sigma[i-1]
T = int(input())
answer = []
for test_case in range(1, T + 1):
a,b = map(int,input().split())
answer.append(f'#{test_case} {odd_sigma[b] - odd_sigma[a-1]}')
print('\n'.join(answer))
27575번: Distinct Parity Excess (acmicpc.net)
27575번: Distinct Parity Excess
A property of any positive integer is its prime parity, which is derived from the count of its distinct prime factors. If this count is even, the prime parity is even; if the count is odd, the prime parity is odd. You are given a sequence of ranges to test
www.acmicpc.net
구간 [a,b]가 주어지면 a이상 b이하의 어떤 정수의 유일한 소인수의 개수가 홀수인 수의 개수와 짝수인 수의 개수의 차이를 묻는 문제
위와 비슷한 방식으로 접근할 수 있다.
1은 소수가 아니니까 2부터 최대 $10^{7}$까지 모든 정수 i에 대하여...
현재 i가 사용되지 않았다면... 해당 i는 소수이며
이 소수 i의 배수는 모두 i를 인수로 가진다.
def get_prime(n):
result = [0]*(n+1)
for i in range(2,n+1):
if result[i] == 0:
for j in range(i,n+1,i):
result[j] += 1
return result
에라토스테네스의 체를 약간 변형시킨 것이다.
result = [1]*(n+1)로 해서... 2부터 int(n**(1/2))까지 순회하는데... result[i] = 1이면
result에서 i보다 큰 i의 배수를 지우고 i가 소수라고 판정하는 거잖아.
반대로 result = [0]*(n+1)로 해서 2부터 n까지 순회하는데 result[i] = 0이면...
i는 소수이고 i 이상 i의 배수는 i를 소인수로 가지니까
여기서 2부터 int(n**(1/2))까지가 아니라 2부터 n까지 하는 이유는???
int(n**(1/2))보다 크고 n보다 작은 어떤 소수 p가 있을 수 있고... 그런 p에 대해서는 result[p] = 1이어야하는데,
int(n**(1/2))까지만 순회하면 result[p] = 0으로 체크가 안되거든
----------------------------------------------------------------------------------------------------------------------
여기까지 하면 모든 정수 2부터 10000000까지에 대하여 각 정수가 가지는 유일한 소인수의 개수를 구했다.
그러면 그 개수가 홀수인 정수와 짝수인 정수로 구분해야한다.
odd = [0]*(10**7+1)
even = [0]*(10**7+1)
for i in range(2,10**7+1):
if counting[i] % 2 == 1:
odd[i] = 1
else:
even[i] = 1
for i in range(2,10**7+1):
odd[i] += odd[i-1]
even[i] += even[i-1]
그리고 구간 쿼리로 주어지니 미리 prefix sum을 구해둔다
n = int(stdin.readline())
for _ in range(n):
a,b = map(int,stdin.readline().split())
x = odd[b] - odd[a-1]
y = even[b] - even[a-1]
print(y-x)
'정수론' 카테고리의 다른 글
2^30으로 나눈 나머지로 2^5으로 나눈 나머지를 구할 수 있을까 (0) | 2024.08.10 |
---|---|
골드바흐의 추측을 이용해 정수 n을 k개의 소수 합으로 표현하기 (0) | 2024.05.24 |
팩토리얼(factorial)의 소수 모듈로 곱셈의 역원(modulo inverse)을 구하는 기본적인 테크닉 (0) | 2024.04.05 |
100부터 1000000000000000까지 모든 정수의 최대공약수는 어떻게 구할 수 있을까 (0) | 2024.03.29 |
페리 수열(farey sequence) 조금 더 깊게3 - 주어진 수의 다음 항을 찾는 방법 - (0) | 2024.02.07 |