문제
크기가 N*N인 행렬 A가 주어진다. 이때, A의 B제곱을 구하는 프로그램을 작성하시오. 수가 매우 커질 수 있으니, A^B의 각 원소를 1,000으로 나눈 나머지를 출력한다.
입력
첫째 줄에 행렬의 크기 N과 B가 주어진다. (2 ≤ N ≤ 5, 1 ≤ B ≤ 100,000,000,000)
둘째 줄부터 N개의 줄에 행렬의 각 원소가 주어진다. 행렬의 각 원소는 1,000보다 작거나 같은 자연수 또는 0이다.
출력
첫째 줄부터 N개의 줄에 걸쳐 행렬 A를 B제곱한 결과를 출력한다.
풀이
지수를 그대로 곱하면 너무 느리기 때문에, 분할 정복을 이용한 거듭제곱이 필요하다. A^B를 계산할 때 B를 절반으로 줄여 구한 뒤, 제곱하거나 한 번 더 곱하는 방식으로 줄여 나간다.
각 단계에서 필요한 것은 행렬 곱셈 하나뿐이다. 따라서 pow(matrix, b) 형태로 재귀를 두고, 짝수면 절반 결과를 두 번 곱하고 홀수면 원본 행렬을 한 번 더 곱하면 된다.
숫자 거듭제곱을 빠르게 구하는 방식이 그대로 행렬에도 적용된다.
코드
import java.io.*;
import java.util.StringTokenizer;
public class Main {
static int N;
static int[][] origin;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
long B = Long.parseLong(st.nextToken());
origin = new int[N][N];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
origin[i][j] = Integer.parseInt(st.nextToken()) % 1000;
}
}
int[][] result = solve(origin, B);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
sb.append(result[i][j]).append(" ");
}
sb.append('\n');
}
System.out.println(sb);
}
private static int[][] solve(int[][] matrix, long exp) {
if (exp == 1)
return matrix;
int[][] tmp = solve(matrix, exp / 2);
tmp = multiply(tmp, tmp);
if (exp % 2 == 1L) {
tmp = multiply(tmp, origin);
}
return tmp;
}
private static int[][] multiply(int[][] m1, int[][] m2) {
int[][] tmp = new int[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
tmp[i][j] += m1[i][k] * m2[k][j];
tmp[i][j] %= 1000;
}
}
}
return tmp;
}
}복잡도
- 시간 복잡도: 행렬 곱셈 기준 전체
- 공간 복잡도: 행렬 저장 공간 기준
마무리
행렬 제곱도 결국 빠른 거듭제곱과 같은 구조다. 지수를 반으로 줄여 나가면 큰 거듭제곱도 충분히 처리할 수 있다.
