이미 알고있는 것들인데 블로그에 정리가 안되어 있어서
1. n!을 소수 p로 나눈 나머지
소수 p에 대하여 n!modp를 구하는 문제가 있다.
단 하나의 n!modp를 구해야한다면...
n!=n∗(n−1)∗(n−2)∗...∗1이므로, 1부터 n까지 O(N)에 다 곱한 다음, p로 나눈 나머지를 구하는 것이 간단하다.
하지만 n이 매우 크면서, 여러가지 n에 대한 n!modp가 필요하다면 이야기가 달라진다.
여러가지 n에 대한 n!modp가 필요하다면 가능한 모든 n에 대해 n!modp를 구해놓고 O(1)로 접근하면 된다.
여기서 다이나믹 프로그래밍에 의해 이전에 구해놓은 값을 저장해둔다면, O(N)에 모든 n에 대해 n!modp를 구할 수 있다.
n!=(n−1)!∗n이므로 dp[n] = dp[n-1] * n으로 구할 수 있다.
n = int(input())
mod = 10**9+7 #매우 큰 소수
factorial = [0]*(n+1)
factorial[0] = 1
for i in range(1,n+1):
factorial[i] = factorial[i-1]*i
print(factorial[n] % mod)
이렇게만 한다면 이론적으로 시간복잡도는 O(N)이지만 n이 매우 클 때 factorial이 매우 커져서 계산이 매우 느려진다.
매우 큰 수의 곱셈이 매우 느려진다는 문제가 있다.
그래서 보통 어떤 수 mod로 나눈 나머지를 요구하며 매번 factorial[i-1] * i를 할때마다 factorial[i]를 mod로 나눠서 저장해둔다면
factorial[i]값이 작아져서 계산이 매우 빨라진다.
이것이 가능한 이유는 모듈로 곱셈, 덧셈 연산의 성질 때문
https://deepdata.tistory.com/399
정수론 - 합과 곱은 왜 계속 나눠도 문제가 없는가? -
1. 문제 https://www.acmicpc.net/problem/1904 1904번: 01타일 지원이에게 2진 수열을 가르쳐 주기 위해, 지원이 아버지는 그에게 타일들을 선물해주셨다. 그리고 이 각각의 타일들은 0 또는 1이 쓰여 있는 낱
deepdata.tistory.com
n = int(input())
mod = 10**9+7 #매우 큰 소수
factorial = [0]*(n+1)
factorial[0] = 1
for i in range(1,n+1):
factorial[i] = factorial[i-1]*i
factorial[i] %= mod #매 곱셈마다 mod로 나눈 나머지를 저장해야 연산이 빨라짐
print(factorial[n])
2. n!의 소수 p에 대한 모듈로 곱셈의 역원
https://deepdata.tistory.com/577
모듈로 연산에서 나눗셈을 하는 방법(모듈로 곱셈의 역원 구하기)
1. 합동식에서 기본적으로 알아야하는 성질 1-1) 양변에 어떤 정수에 대한 덧셈이나 뺄셈을 하더라도 상관없다. a≡b(modp)이고 c≡d(modp)이면, a±c≡b±d(modp) 그러므로, c = d
deepdata.tistory.com
이항계수 nCr=n!(n−r)!r!을 어떤 수 p로 나눈 나머지를 구할려면 1(n−r)!r!modp가 필요하다.
(n!)−1modp=1n!modp를 어떻게 구할 수 있을까?
모듈로 연산의 성질을 잘 생각해본다면.. 아주 나이브하게 다음과 같이 구해볼 수 있을 것 같다
n!∗(n!)−1modp≡(1∗2∗3∗...∗n)∗(n−1∗(n−1)−1∗...∗1−1)modp≡1
n!은 1부터 n까지의 곱이니까, 1부터 n까지의 역원을 각각 곱해버리면 1이 될 것이다.
따라서 n!의 모듈로 곱셈의 역원은 정수 1부터 n까지의 역원을 곱한 것과 같다.
3. 정수 n의 소수 모듈로 곱셈에 대한 역원
어떤 정수 n의 소수 모듈로 곱셈에 대한 역원은 어떻게 구할 수 있을까?
https://deepdata.tistory.com/577
모듈로 연산에서 나눗셈을 하는 방법(모듈로 곱셈의 역원 구하기)
1. 합동식에서 기본적으로 알아야하는 성질 1-1) 양변에 어떤 정수에 대한 덧셈이나 뺄셈을 하더라도 상관없다. a≡b(modp)이고 c≡d(modp)이면, a±c≡b±d(modp) 그러므로, c = d
deepdata.tistory.com
소수 p에 대한 모듈로 곱셈의 역원을 구하고 싶다면 페르마의 소정리를 이용하면 O(logp)에 구할 수 있다.
소수 p보다 작은 1이 아닌 정수 n은 p와 서로소이므로 페르마의 소정리에 의해 np−1≡1modp
식을 변형하면 np−2∗n≡1modp이므로, n의 소수 p에 대한 모듈로 곱셈의 역원은 np−2
분할정복을 이용한 거듭제곱을 이용하면 np−2를 빠르게 구할 수 있다.
n**(p-2)를 하고 %p를 하면 될 것이라고 생각할 수 있지만... 위에서 설명한 (n!을 p로 나눈 나머지)와 같은 이유로
n이 매우 크고 p가 매우 크면 거듭제곱이 매우 커져서 나중에 p로 나눈 나머지를 구하기까지 시간이 너무 오래걸린다
그래서 n을 p-2번 곱할때까지 매번 곱할때마다 p로 나눈 나머지끼리 곱해야한다.
n mod p * n mod p * n mod p * .... n mod p 이런 식으로
python의 pow()함수는 pow(n, p-2, p)로 간단하게 이를 구현하게 해준다
따라서 (n!)^-1 mod p는 다이나믹 프로그래밍을 이용해서 다음과 같이 구할 수 있다.
dp = [0]*(n+1)
dp[0] = 1
dp[i] = dp[i-1] * pow(i,mod-2,mod)
dp[i] %= mod
n = int(input())
mod = 10**9+7 #매우 큰 소수
factorial = [0]*(n+1)
factorial[0] = 1
factorial_inverse = [0]*(n+1)
factorial_inverse[0] = 1
for i in range(1,n+1):
#n! mod p
factorial[i] = factorial[i-1]*i
factorial[i] %= mod #매 곱셈마다 mod로 나눈 나머지를 저장해야 연산이 빨라짐
#(n!)^-1 mod p = 1^-1 * 2^-1 ... n^-1 mod p
# = (n-1)^-1 * n^-1 mod p
factorial_inverse[i] = factorial_inverse[i-1] * pow(i,mod-2,mod)
factorial_inverse[i] %= mod
print(factorial[n])
4. 정수 n의 모듈로 곱셈에 대한 역원
정수 n의 모듈로 p에 대한 곱셈의 역원을 다음과 같은 점화식으로 쉽게 구할 수 있다는 것이 알려져있다.
모듈로 p를 n으로 나눈 몫 p//n과 n으로 나눈 나머지 p % n(p mod n)를 이용하면 다음과 같이 나타낼 수 있다.
p=(p//n)∗n+(pmodn)
양변에 mod p를 한다면 p는 p로 나눈 나머지가 0이므로.. 좌변은 0이고
0≡(p//n)∗n+(pmodn)modp
양변에 (p//n)∗nmodp를 빼준다면...
−(p//n)∗n≡(pmodn)modp
따라서 ((−(p//n)n)−1≡(pmodn)−1modp
n−1를 구하기 위해 양변에 (−p//n)을 곱해준다면...
n−1modp≡(−p//n)∗(pmodn)−1modp
여기서 핵심은 p mod n는 p를 n으로 나눈 나머지이므로 0,1,2,...,n-1중 하나이니 항상 n보다 작다.
그러므로 dp[n]을 n의 p에 대한 모듈로 곱셈의 역원이라고 정의하자.
dp[n] = -(p//n) * dp[p%n]라는 점화식으로 구할 수 있다.
p%n이 n보다 작으므로 dp[n]을 구하기 위해 dp[p%n]가 이전에 구해져 있다.
여기서 1의 p에 대한 곱셈의 역원은 1이 자명하므로 dp[1] = 1이다.
점화식으로 정수 n에 대한 역원을 미리 구해놓는다면, 팩토리얼의 역원은 pow(i,mod-2,mod) 대신에
이전에 구해놓은 정수 n에 대한 역원을 이용해서...
factorial_inverse[i] = factorial_inverse[i-1] * inverse[i]
n = int(input())
mod = 10**9+7 #매우 큰 소수
factorial = [0]*(n+1)
factorial[0] = 1
inverse = [0]*(n+2)
inverse[1] = 1
factorial_inverse = [0]*(n+1)
factorial_inverse[0] = 1
for i in range(1,n+1):
#n^-1 mod p = -p//n * (p%n)^-1 mod p
inverse[i+1] = -(mod//(i+1)) * inverse[mod % (i+1)]
inverse[i+1] %= mod
#n! mod p
factorial[i] = factorial[i-1]*i
factorial[i] %= mod #매 곱셈마다 mod로 나눈 나머지를 저장해야 연산이 빨라짐
#(n!)^-1 mod p = 1^-1 * 2^-1 ... n^-1 mod p
# = (n-1)^-1 * n^-1 mod p
#factorial_inverse[i] = factorial_inverse[i-1] * pow(i,mod-2,mod)
factorial_inverse[i] = factorial_inverse[i-1] * inverse[i]
factorial_inverse[i] %= mod
print(factorial[n])
5. 페르마의 소정리를 이용한 n!의 소수 모듈로 곱셈에 대한 역원
https://deepdata.tistory.com/979
이항정리를 이용한 거듭제곱의 합 1^k+2^k+3^k+...+n^k 을 구하는 방법
1. 이항정리(binomial theorem) 0이상의 정수 n과 음이 아닌 정수 x,y에 대하여, (x+y)^{n} = \sum_{k = 0}^{n}\binom{n}{k}x^{k}y^{n-k} https://en.wikipedia.org/wiki/Binomial_theorem Binomial theorem - Wikipedia From Wikipedia, the free en
deepdata.tistory.com
페르마의 소정리를 이용하면 아주 간단하게 n!의 소수 p 모듈로 곱셈에 대한 역원의 점화식을 유도할 수 있다.
n이 소수 p보다 작다면 n! = 1*2*3*...*n에서 1부터 n은 모두 소수 p의 인수가 될 수 없으므로
n!과 소수 p는 서로소가 된다.
따라서 페르마의 소정리에 의해 (n!)^{p-1} \equiv 1 mod p
역시 n!을 빼낸다면... (n!)^{p-2} n! \equiv 1 mod p
그러므로 (n!)^{-1} mod p = (n!)^{p-2}
그런데 (n!)^{p-2} n! \equiv 1 mod p을 다시 자세히 살펴보자. n! = (n-1)! * n이므로...
(n!)^{p-2} * n * (n-1)! \equiv 1 mod p
따라서 (n-1)!^{-1} mod p = (n!)^{p-2} * n = (n!)^{-1} * n
dp[n]을 n!의 소수 p 모듈로 곱셈에 대한 역원이라고 정의하자.
먼저 dp[n] = (n!)^{-1} mod p = (n!)^{p-2}를 분할정복을 이용한 거듭제곱으로 구해놓는다.
여기서 n! mod p는 이미 앞에서 구해놓았다.
dp[n-1] = dp[n] * n으로 n부터 시작해서 거꾸로 n,n-1,n-2,...,1까지 순회하면
n! 소수 p 모듈로 곱셈에 대한 역원을 다이나믹 프로그래밍으로 간단하게 구할 수 있다
n = int(input())
mod = 10**9+7 #매우 큰 소수
factorial = [0]*(n+1)
factorial[0] = 1
for i in range(1,n+1):
#n! mod p
factorial[i] = factorial[i-1]*i
factorial[i] %= mod #매 곱셈마다 mod로 나눈 나머지를 저장해야 연산이 빨라짐
factorial_inverse = [0]*(n+1)
factorial_inverse[n] = pow(factorial[n], mod-2, mod)
#(n-1)!^-1 mod p = n!^-1 mod p * n
for i in range(n,0,-1):
factorial_inverse[i-1] = factorial_inverse[i] * i
factorial_inverse[i-1] %= mod
print(factorial[n])
PS를 위한 정수론 - (4) 이항 계수 (nCr mod P) 구하는 다양한 방법
이항 계수 _{n}C_r을 소수 p로 나눈 나머지를 빠르게 구하는 다양한 방법들을 알아보자. 기본적으로 특정 n과 r에 대해서 이항 계수 _{n}C_r을 구하는 시간은 O(1)이 되어야 할 때, 즉, 많은 쿼
rebro.kr
이항계수 (nCr) mod p 계산하기
nCr = n! / (n-r)! * r! 일 때, nCr mod prime을 빠르게 계산하는 방법에 대해서 얘기하려 한다. nCr 한 쿼리는 O(1)에 처리되어야 한다고 가정한다. (즉 전처리 한번 한 이후 nCr을 하나 계산하는데 O(1)) 1. O(n^2
koosaga.com
'정수론' 카테고리의 다른 글
골드바흐의 추측을 이용해 정수 n을 k개의 소수 합으로 표현하기 (0) | 2024.05.24 |
---|---|
약수를 세는 것보다 배수를 세는 것이 더 쉽다(홀수인 약수의 합, 유일한 소인수의 개수를 구하는 방법) (0) | 2024.04.11 |
100부터 1000000000000000까지 모든 정수의 최대공약수는 어떻게 구할 수 있을까 (0) | 2024.03.29 |
페리 수열(farey sequence) 조금 더 깊게3 - 주어진 수의 다음 항을 찾는 방법 - (0) | 2024.02.07 |
페리 수열(farey sequence) 조금 더 깊게2 - 페리 수열의 대칭성과 페리 수열의 합 - (0) | 2024.02.07 |