# 7-1. 소수 구하기
- 전에 정리했지만 소수를 구하는 대표적인 판별법으로 에라토스테네스의 체가 있다.
- 주어진 범위의 수 리스트를 생성한다.
- 1은 소수가 아니므로 삭제 하고, 리스트 2부터 시작한다.
- 선택한 수의 배수를 모두 삭제한다.
- 이미 지운 수는 다시 지우지 않고, 지워지지 않은 수를 선택하며 리스트의 끝까지 반봇한다.
- 이중 for문이지만 실제 시간 복잡도는 배수 삭제 연산으로 바깥쪽 for문을 생략하는 경우가 많아 O(Nlog(logN))이다.
- 또한 N = a*b일때, a와 b는 N의 제곱근보다 클 수 없으므로 탐색은 N의 제곱근까지만 탐색한다.
https://www.acmicpc.net/problem/1929
import math
M, N = map(int, input().split())
A = [0]*(N+1)
for i in range(2, N+1):
A[i]=i # 수 리스트 초기화
for i in range(2, int(math.sqrt(N))+1): # 제곱근까지만 수행
if A[i]==0:
continue
for j in range(i+i, N+1, i): # 배수 지우기
A[j]=0 # 현재 수가 소수가 아님을 표시
for i in range(M, N+1):
if A[i] != 0:
print(A[i]) # 소수인 값 출력
https://www.acmicpc.net/problem/1456
import math
Min, Max = map(int, input().split())
A = [0]*10000001
for i in range(2, len(A)):
A[i]=i
for i in range(2, int(math.sqrt(len(A))+1)): # 제곱근까지만 수행
if A[i]==0:
continue
for j in range(i+i, len(A), i): # 배수 지우기
A[j]=0
count = 0
for i in range(2, 10000001):
if A[i]!=0: # 소수인 경우
temp = A[i]
while A[i]<=Max/temp: # 변수 표현 범위를 넘어갈 수 있어 이항 정리로 처리
if A[i]>=Min/temp: # 실제로는 A[i]*temp >= Min
count +=1
temp = temp*A[i]
print(count)
- 먼저 에라토스테네스의 체를 이용해 입력범위(10^14) 내의 소수를 모두 구한다.
- 이후 주어진 소수들의 N 제곱값이 A~B 범위 내에 존재하는지 판별하여 개수를 센다.
- B와의 비교를 위해 N을 제곱한 값이 변수 표현 범위를 초과할 수 있다. 이때 오류 방지를 위해 소수 N과 B/N^(k-1)을 비교한다.
https://www.acmicpc.net/problem/1747
import math
N= int(input())
A = [0]*10000001
for i in range(2, len(A)):
A[i]=i
for i in range(2, int(math.sqrt(len(A))+1)): # 제곱근까지만 수행
if A[i]==0:
continue
for j in range(i+i, len(A), i): # 배수 지우기
A[j]=0
def isPalindrome(target): # 팰린드롬 수 판별 함수
temp = list(str(target))
s = 0
e = len(temp)-1
while(s<e):
if temp[s]!=temp[e]:
return False
s+=1
e-=1
return True
i=N
while True: # N부터 1씩 증가시키면서 소수와 팰린드롬 수가 맞는지 판별
if A[i]!=0:
result = A[i]
if isPalindrome(result): # 첫 팰린드롬이자 소수인 수가 나오면 종료
print(result)
break
i+=1
- 먼저 최대 범위에 해당하는 모든 소수를 구한다.
- 구한 소수 리스트에서 N보다 크거나 같으면서 팰린드롬인 수를 찾아낸다.
- 팰린드롬 수 판별 시 숫자를 리스트 형태로 변환하면 편리하게 구현 가능하다.
https://www.acmicpc.net/problem/1016
import math
Min, Max = map(int, input().split())
Check = [False]*(Max-Min+1)
for i in range(2, int(math.sqrt(Max)+1)):
pow = i*i # 제곱수
start_index = int(Min/pow)
if Min%pow != 0:
# 나머지가 있는 경우 1을 더해 Min보다 큰 제곱수에서 시작하도록 설정
start_index += 1
for j in range(start_index, int(Max/pow)+1): # 제곱수의 배수 형태로 탐색
# j*pow가 Max보다 작으면 제곱수를 True로 변경
# int((j*pow))-Min 값이 제곱수에 해당하는 값의 인덱스이다.
Check[int((j*pow)-Min)]=True
count = 0
for i in range(0, Max - Min+1):
if not Check[i]:
count += 1
print(count)
- 에라토스테네스의 체 알고리즘 방식을 제곱수 판별 로직에 적용해야 하는 문제.
- 먼저 2의 제곱수인 4부터 max값까지 제곱수를 찾는다.
- 이후 탐색한 리스트에서 제곱수로 확인되지 않은 수의 개수를 센 후 출력한다.
# 7-2. 오일러 피
- 오일러 피 함수 P[N]는 1부터 N까지 범위에서 N과 서로소인 자연수의 개수를 뜻한다.
- 먼저 구하고자 하는 오일러 피의 범위만큼 리스트를 자기 자신의 인덱스 값으로 초기화한다.
- 2부터 시작하여 현재 리시트의 값과 인덱스가 같으면(소수일 때) 현재 선택된 숫자(K)의 배수에 해당하는 수를 리스트에 끝까지 탐색하며 P[i] =P[i] - P[i]/K 연산을 수행한다. (i는 K의 배수)
- 예를 들어, 리스트 생성 후 2를 선택했다면 2의 모든 배수마다(2포함) P[i] = P[i] - P[i]/2 연산을 수행한다. P[4] = P[4] - P[4]/2 = 2, P[6] = P[6] - P[6]/2 = 3, ...
- 리스트 끝까지 위 과정을 반복하여 오일러 피 함수를 완성한다.
- 수학적 원리
- 처음 초기화된 리스트는 서로소가 될 수 있는 후보의 개수이다. 즉, 6이라면 후보(1,2,3,4,5,6)으로 6 값을 가진다.
- 다음 리스트의 처음부터 탐색하면서 2의 배수인 수들이 탈락하며 P[6] = 3이 된다.(후보 1,3,5)
- 다음으로 3의 배수인 수들이 탈락하여 P[6] = 2가 된다. 후보(1, 5)
- 3과 2의 공배수, 즉 중복 삭제를 막기 위해 지속적으로 업데이트를 하고, 업데이트한 값을 기준으로 연산식을 수행해야 한다.
https://www.acmicpc.net/problem/11689
import math
N = int(input()) # 소인수 표현
result = N # 결과값
for p in range(2, int(math.sqrt(N))+1): # 제곱근까지만 진행
if N%p == 0: # p가 N의 소인수이면
result -= result/p # 결과값 업데이트(오일러 피 함수)
while N%p==0:
# 2^7*11이라면 2^7을 없애고 11만 남김
N/=p
if N>1: # 반복문에서 제곱근까지만 탐색했으므로 1개의 소인수가 누락되는 케이스 처리
# 즉, n이 1보다 크면 n이 마지막 소인수라는 뜻이다. 따라서 마지막으로 업데이트
result -= result/N
print(int(result))
- GCD(n,k) = 1, 즉 n과 k가 서로소인(최대공약수가 1이므로!) 값의 개수를 구한다.
- 처음 result값은 N으로 초기화 하고, 오일러 피 함수를 통해 서로소가 아닌 수가 있다면(N%p==0) 소인수를 제거한다.
- 마지막 소인수까지 제거해준다.
# 7-3. 유클리드 호제법
- 유클리드 호제법이란 두 수의 최대 공약수를 구하는 알고리즘이다.
- 일반적으로는 두 수를 소인수 분해 후 공통된 소수들의 곱으로 표현할 수 있으나, 유클리드 호제법은 더 간단!
- 큰 수를 작은 수로 나누는 MOD 연산을 수행한다.
- 앞 단계에서의 작은 수와 MOD 연산 결과값(나머지)로 MOD 연산을 수행한다.
- 위 과정을 반복하다 나머지가 0이 되는 순간의 작은 수를 최대 공약수로 선택한다.
https://www.acmicpc.net/problem/1934
def gcd(a,b):
if b==0:
return a
else:
return gcd(b, a%b) # 재귀 형태로 구현
t = int(input())
for i in range(t):
a,b = map(int, input().split())
result = a*b/gcd(a,b)
print(int(result))
- 최소공배수는 두 수의 곱/최대 공약수로 구할 수 있다.
https://www.acmicpc.net/problem/1850
def gcd(a,b):
if b==0:
return a
else:
return gcd(b, a%b) # 재귀 형태로 구현
a,b = map(int, input().split())
result = gcd(a,b)
while result>0 : # result값만큼 반복하여 1 출력
print(1, end='')
result -=1
- 주어진 두 길이의 최대공약수를 구한 후 길이를 1로 나타내 구할 수 있다.
https://www.acmicpc.net/problem/1033
N = int(input()) # 재료의 개수
A = [[] for _ in range(N)] # 인접 리스트
visited = [False]*N # DFS에서 탐색 여부 저장 리스트
D = [0]*N # 각 노드값 저장 리스트
lcm = 1 # 최소 공배수
def gcd(a, b): # 최대공약수 함수 구현
if b == 0:
return a
else:
return gcd(b, a%b)
def DFS(v): # DFS 탐색 함수 구현
visited[v]=True
for i in A[v]:
next = i[0]
if not visited[next]: # 방문하지 않은 노드면
D[next] = D[v]*i[2]//i[1] # 다음 노드의 값 = 현재 노드의값*비율
DFS(next)
for i in range(N-1):
a,b,p,q = map(int, input().split())
A[a].append((b,p,q))
A[b].append((a,q,p))
lcm *= (p*q//gcd(p,q)) # 최소 공배수는 두 수의 곱을 최대 공약수로 나눈 것
D[0]=lcm # 0번 노드에 최소 공배수 저장
DFS(0)
mgcd=D[0]
for i in range(1, N):# DFS를 이용해 업데이트 된 D리스트의 값들의 최대 공약수 계산
mgcd = gcd(mgcd, D[i])
for i in range(N): # D 리스트의 각 값을 최대 공약수로 나눠 정답 출력
print(int(D[i]//mgcd), end=' ')
- 먼저 각 자료의 비율 자료를 그래프로 구현한다. (A 리스트에 저장)
- 데이터를 저장할 때마다 비율과 관련된 수들의 최소 공배수를 업데이트 한다.
- 임의의 시작점(D[0])에 최대 공배수 값을 저장한다.
- 임의의 시작점에서 DFS로 탐색을 수행하면서 각 노드의 값을 이전 노드의 값과의 비율 계산을 통해 계산하고 저장한다.
- 노드 0에서 시작하는 경우, 0->4->1->2->3 순으로 탐색하게 된다.
- 예를 들어, 노드 4번(D[next])에는 0번 노드값(D[v])*비율(i[2]//i[1]) 값이 저장된다.
- 각 노드의 값을 모든 노드의 최대 공약수로 나눈 뒤 출력한다.
# 7-4. 확장 유클리드 호제법
- 확장 유클리드 호제법의 목적은 방정식의 해를 구하는 것이다.
- 해를 구하고자 하는 방정식은 ax + by = c(a,b,c,x,y는 정수)
- 이때 위 방정식은 c%gcd(a,b) = 0인 경우에만 정수해를 가진다.
- 5x+9y = 2일 때 이 식을 만족하는 정수 x,y를 구한다. 이때 c의 최솟값이 gcd(5,9)=1이므로 식을 5x+9y=1로 다시 놓고 진행한다.
- a,b로 유클리드 호제법을 방복 실행하며 몫, 나머지를 저장한다. 반복은 나머지가 0이 되면 중단한다.
- 반복문으로 구한 나머지와 몫을 이용하여 거꾸로 올라가며 x = y', y = x'-y'*q를 계산한다. x'는 이전 x, y'는 이전 y', q는 현재 보고있는 몫을 의미한다. 처음 시작하는 x,y는 각각 1,0으로 지정하여 역계산을 진행한다.
- 이렇게 재귀 방식으로 알아낸 최종 x,y는 ax +by = gcd(a,b)를 만족한다. c/gcd(a,b) = K로 가정하면 최초 방정식의 해는 Kx, Ky로 간단히 구할 수 있다.
https://www.acmicpc.net/problem/21568
잉? 제출함이 없다.
a,b,c = map(int, input().split())
def gcd(a, b):
if b == 0:
return a
else:
return gcd(b, a%b)
def Execute(a,b):
ret = [0]*2
if b == 0: # 재귀함수를 중단하고 x,y를 1,0으로 설정하여 return
ret[0] = 1
ret[1] = 0
return ret
q = a//b
v = Execute(b, a%b) # 재귀 형태로 유클리드 호제법 수행
ret[0] = v[1] # 역순으로 올라오면서 x,y를 계산하는 로직
ret[1] = v[0] - v[1]*q
return ret
mgcd = gcd(a,b)
if c%mgcd != 0: # c가 최대 공약수의 배수가 아니면 -1 출력
print(-1)
else:
mok = int(c/mgcd)
# 나머지(b)가 0이 될 때까지 재귀 함수를 호출하는 유클리드 호제법 함수 호출
ret = Execute(a,b)
# 결과값에 c/최대 공약수의 값을 곱한 후 해당 값 출력
print(ret[0]*mok, end=' ')
print(ret[1]*mok)
- 앞서 소개한 확장 유클리드 호제법을 그대로 구현.
# 회고
많은 코테의 기본이 되는 정수론을 배웠다.
이런 기본을 응용하면 더 어려워지는 듯.
확장 유클리드 호제법은 걍 뭔소린지 모르겠다...ㅎ 다시 읽어봐야지.
'코딩테스트' 카테고리의 다른 글
[Python] 그리디 문제 풀이 정리(2) (0) | 2024.05.07 |
---|---|
[Python] 그리디 문제 풀이 정리 (0) | 2024.05.04 |
[Python] DFS 문제 풀이 정리 2 (0) | 2024.04.14 |
[Python] DFS 문제 풀이 정리 (0) | 2024.04.12 |
[Python] 그리디 (0) | 2024.04.09 |