풀이1 이항계수의 다항식성질
이항계수의 다항식의 성질을 생각할 수 있지만 반복문을 사용하기 때문에 시간초과가 발생한다.
4백만 크기의 n을 입력하면 시간초과는 뻔하기 때문에 백준에 입력하지는 않았다.
<x+1의 n승 다항식 계수 구하기>
현재 배열의 값들을 오른쪽으로 한칸씩 옮기는데 오른쪽 끝부터 오른쪽으로 한 칸씩 옮긴다.
옮기면서 원래 그 자리에 있던 수와 더한다.
이를 마치면 (n k) 줄에 해당하는 식이 완성되어있고 이 배열의 k번째 index의 값을 출력하면 된다.
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] arr = new int[N+1];
arr[0]=1;
for(int i=0;i<N;i++) {
for(int j=i;j>=0;j--) {
arr[j+1]=(arr[j+1]+arr[j])%1000000007;
}
}
System.out.println(arr[sc.nextInt()]);
}}
풀이2 배열을 이용한 분할정복
결국 2배씩 앞당겨서 찾는 방법을 생각해야했는데, 위 다항식을 분할정복을 통해 구현할 수 있다.
예를 들면 0번째줄부터 4번째 줄을 나타내면 다음과 같이 (x+1)^n의 다항식 계수로 표현할 수 있을 것이다.
1 = (x+1)의 0승
1 1 = x+1
1 2 1 = x^2 + 2x + 1 = (x+1)^2
1 3 3 1 = (x+1)^3
이를 통해 (n k)의 결과값은 (x+1)^n의 k번째 항의 계수임을 알 수 있고
규칙성을 이용해 n을 절반으로 쪼개가며 다항식의 곱으로 표현할 수 있다.
(x+1)^n = (x+1)^n/2 * (x+1)^n/2
메소드
(x^2+2x+1) (x+1)의 결과를 어떻게 표현할 수 있을까?
배열로 나타내면 {1, 2, 1, 0}, {1, 1, 0, 0} 이다.
{1, 2, 1, 0}, {1, 1, 0, 0}: 각 index는 x가 몇번 곱해져있는지 알려준다. 오른쪽부터 계산하면 → {0, 0, 0, 1}
{1, 2, 1, 0}, {1, 1, 0, 0}: index[0]을 곱하면 자리는 변하지 않는다 → {0, 0, 1, 1}
{1, 2, 1, 0}, {1, 1, 0, 0}: index[1]을 곱하면 자리는 +1만큼 변한다 → {0, 0, 3, 1}
{1, 2, 1, 0}, {1, 1, 0, 0}: index[0]을 곱하면 자리는 변하지 않는다 → {0, 2, 3, 1}
다항식의 연산과 분할정복을 합해봤지만 시간초과였다
import java.util.*;
public class Main {
static int N;
static long[] arr;
static long[] pascal(int N) {
if(N==0 || N==1) {
return arr;
}
long[] before = pascal(N/2);
long[] answer = cal(before,N/2+1,before,N/2+1);
if(N%2==0) {
return answer;
}else {
return cal(answer,N-1,arr,2);
}
}
static long[] cal(long[] A, int lenA,long[] B, int lenB) {
long[] C = new long[N+1];
for(int i=0;i<lenA;i++) {
for(int j=0;j<lenB;j++) {
long num = A[i]*B[j]%1000000007;
C[i+j]+=num;
}
}
return C;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
arr = new long[N+1];
arr[0]=1;
arr[1]=1;
System.out.println(pascal(N)[sc.nextInt()]);
}}
풀이 3 페르마 소정리를 이용한 분할정복
성질을 이용해서 4000000 사백만의 n에 도달하는 것은 어렵다고 결론지었다.
결국 자체식인 (n k) = n!/k!(n-k)! 을 사용해야했다.
나눗셈이 들어간 식에서 모듈러 연산을 사용할 수 없기 때문에 모듈러 연산을 사용하기 위해 등식을 이용해 식을 변경한다.
(n k) = B/A = B*A^-1 = n!/k!(n-k)! → A = k!(n-k)! → A^-1 = 1 / k!(n-k)! 의 식으로 나타낼 수 있고 페르마의 소정리를 적용한다.
<페르마의 소정리: p가 모듈러>
A^(p-1) ≡ 1
A^(p-2) ≡ A^-1
이에 위의 식은 (n k) = B/A = n!/k!(n-k)! → n! * {k!(n-k!)}^(1000000007-2) 가 된다.
메소드1
각각의 팩토리얼을 계산하는 메소드를 만든다.
예시) n! → n번 곱하면서 모듈러 연산진행
메소드2
분할정복으로 지수승을 구하는 메소드를 만든다.
import java.util.*;
public class Main {
static long MOD = 1000000007;
static long Factorial(int n) {
long sum=1;
for(int i=1; i<=n; i++) {
sum=(sum*i)%MOD;
}
return sum;
}
static long power(long a, long b) {
if(b==1) {
return a;
}
long before = power(a,b/2)%MOD;
long now = before*before%MOD;
if(b%2==0) {
return now;
}else {
return now*a%MOD;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int K = sc.nextInt();
long A = Factorial(N),B = Factorial(K),C = Factorial(N-K);
long D = B*C%MOD;
System.out.println(A*power(D,MOD-2)%MOD);
}}
'Study > Baekjoon' 카테고리의 다른 글
Baekjoon2606: 바이러스 (0) | 2022.01.10 |
---|---|
Baekjoon1260: DFS와 BFS, 메소드 이해와 구현 (0) | 2022.01.04 |
Baekjoon11444: 피보나치 수 6 (0) | 2021.12.26 |
Baekjoon10830: 행렬 제곱 (0) | 2021.12.26 |
Baekjoon1629: 곱셈 (0) | 2021.12.25 |