← 문제 풀이 목록

백준 1629번 행렬 거듭제곱 문제의 원리를 파헤쳐보자

/ 8분 분량 / 문제 풀이

복잡해 보이는 행렬 거듭제곱 문제를 O(log B)의 시간 복잡도로 해결하는 알고리즘에 대해 공부했습니다. 단순 반복이 아닌, 분할정복과 모듈러 연산의 조합이 어떻게 성능 향상을 가져오는지, 그리고 이 원리가 어떻게 풀고자 하는 문제에 적용되는지 이해할 수 있었습니다.

복잡해 보이는 행렬 거듭제곱 문제를 O(log B)의 시간 복잡도로 해결하는 알고리즘에 대해 공부했습니다. 단순 반복이 아닌, 분할정복과 모듈러 연산의 조합이 어떻게 성능 향상을 가져오는지, 그리고 이 원리가 어떻게 풀고자 하는 문제에 적용되는지 이해할 수 있었습니다.

학습 주제

  • 주제: 행렬 거듭제곱과 모듈러 연산 최적화
  • 대화 제목: 백준 1629번 문제 풀이 및 행렬 거듭제곱 적용
  • 학습 날짜: 2026년 1월 31일

질문과 탐구

평소 저는 주어진 문제를 해결하기 위해 기본적인 반복문을 사용하는 방식에 익숙했습니다. 하지만 백준 1629번 문제에서처럼 지수가 매우 커질 경우, 단순 반복은 시간 초과를 피할 수 없다는 것을 경험했습니다. 결국 AI에게 이를 해결할 알고리즘에 대해 질문했고 O(log b)로 해결 가능한 알고리즘이 존재한다는 말에 다음과 같은 질문들로 이어졌습니다.

  • a^b % c를 O(b)가 아닌 O(log b)로 계산하는 방법은 무엇인가?
  • 핵심 아이디어는 무엇이며, 어떻게 구현되는가?
  • 행렬 거듭제곱에도 이 원리를 적용할 수 있는가?
  • 행렬 곱셈에서 모듈러 연산은 어떻게 적용해야 정확한가?

이러한 궁금증을 바탕으로, '분할정복 거듭제곱(modular exponentiation)'이라는 알고리즘을 배우게 되었습니다.

핵심 학습 내용

1. 분할정복 거듭제곱: O(log B)

아이디어는 지수 b를 절반씩 줄여나가면서 재귀적으로 계산하는 것입니다.

  • 핵심 수학 성질: (A * B) % C = ((A % C) * (B % C)) % C
    이 성질 덕분에 우리는 각 단계마다 모듈러 연산을 적용하여 값의 크기를 제어할 수 있습니다.
  • 알고리즘 구조:
    • b가 0이면 1 (또는 단위행렬)을 반환합니다. (기저 조건)
    • b가 짝수라면: A^b = (A^(b/2)) * (A^(b/2))
    • b가 홀수라면: A^b = (A^(b/2)) * (A^(b/2)) * A

이 재귀적인 구조는 지수 b를 절반씩 줄여나가므로, 연산 횟수는 log2(b)에 비례하게 됩니다. 이것이 바로 O(log b)의 시간 복잡도를 달성하는 원리입니다.

// 예시 (모듈러 거듭제곱)
long long modpow(long long a, long long b, long long c) {
    if (b == 0) return 1; // 기저 조건: b가 0이면 1 반환

    long long remainder = modpow(a, b / 2, c); // 지수를 절반으로 나눠 재귀 호출
    remainder = (remainder * remainder) % c;   // 재귀 결과 제곱 후 모듈러 연산

    if (b % 2 == 1) // b가 홀수라면, 마지막에 a를 한 번 더 곱함
        remainder = (remainder * a) % c;

    return remainder;
}

2. 행렬 거듭제곱으로의 확장

이 분할정복 원리는 행렬에도 그대로 적용됩니다. 행렬 곱셈은 교환 법칙은 성립하지 않지만, 결합 법칙은 성립하기 때문입니다.

  • 행렬 곱셈: (A * B) * C = A * (B * C)
  • 행렬 거듭제곱: A^b를 계산할 때, A 행렬의 곱셈 연산을 반복하는 대신, 위에서 설명한 재귀적인 분할정복 방식을 사용합니다.
    • b=0일 때: 단위행렬(Identity Matrix)을 반환합니다. 단위행렬은 곱셈에 대한 항등원 역할을 합니다.
    • b가 짝수: A^b = (A^(b/2)) * (A^(b/2))
    • b가 홀수: A^b = (A^(b/2)) * (A^(b/2)) * A

이때, 행렬 곱셈 자체는 O(N^3)의 복잡도를 가지지만 (N은 행렬의 차원), 재귀 호출 횟수가 O(log b)이므로 전체 시간 복잡도는 **O(N^3 log b)**가 됩니다. 이는 단순히 b번 행렬을 곱하는 것보다 훨씬 효율적입니다.

3. 모듈러 연산의 올바른 적용

행렬 거듭제곱 과정에서 값이 매우 커질 수 있으므로, 문제에서 요구하는 모듈러 연산을 각 단계마다 올바르게 적용하는 것이 중요합니다.

  • 핵심 원리: 모듈러 산술의 성질 (a + b) % c = ((a % c) + (b % c)) % c와 (a * b) % c = ((a % c) * (b % c)) % c
  • 올바른 적용: 행렬 곱셈 Z[i][j] = (Z[i][j] + X[i][k] * Y[k][j]) % 1000; 와 같이 각 곱셈 결과와 이전 누적합을 더할 때마다 모듈러 연산을 적용해야 합니다. 단순히 (X[i][k] * Y[k][j]) % 1000만 적용하면 누적합 과정에서 overflow가 발생하거나, 문제에서 요구하는 0~999 범위를 벗어날 수 있습니다.
// 올바른 행렬 곱셈 함수 (mod 1000 적용)
vector<vector<long long>> mat_mul(const vector<vector<long long>>& X, const vector<vector<long long>>& Y) {
    vector<vector<long long>> Z(N, vector<long long>(N, 0));
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                // 누적합과 곱셈 모두에 mod 적용
                Z[i][j] = (Z[i][j] + X[i][k] * Y[k][j]) % 1000;
            }
        }
    }
    return Z;
}

이해한 내용

이번 학습을 통해 왜 이러한 알고리즘이 효율적인지에 대한 이해를 얻었습니다.

  • 새롭게 알게 된 것:
    • 분할정복이 거듭제곱 문제에 어떻게 적용될 수 있는지.
    • 행렬 곱셈의 성질(결합법칙)을 이용해 재귀적인 계산이 가능하다는 점.
    • 모듈러 연산이 각 단계에서 어떻게 적용되어야 정확한 결과를 보장하는지.
  • 이전 지식과의 연결:
    • 기존에 알던 pow(a, b) 함수의 O(b) 복잡도를 O(log b)로 개선하는 원리를 확실히 이해했습니다.
    • 수학 시간에 배웠던 모듈러 산술의 성질이 실제 알고리즘 구현에서 얼마나 중요한 역할을 하는지 체감했습니다.
    • 행렬 곱셈 자체의 복잡도 O(N^3)와 재귀 호출의 O(log B)가 결합하여 O(N^3 log B)라는 복잡도가 도출되는 과정을 명확히 이해했습니다.

추가 학습 계획

이번 학습을 통해 행렬 거듭제곱의 핵심 원리를 파악했지만, 더 깊이 탐구하고 싶은 부분이 있습니다.

  • 더 깊이 공부하고 싶은 부분:
    • 행렬 곱셈의 복잡도를 O(N^3)보다 더 빠르게 개선하는 알고리즘 (예: Strassen 알고리즘)에 대해 알아보기.
    • 선형대수학에서 행렬의 다양한 연산들이 어떻게 컴퓨터 알고리즘과 연결되는지 더 깊이 탐구.
  • 관련 자료 찾기:
    • 행렬 거듭제곱 관련 알고리즘 강의 영상 및 블로그 글.
    • 각종 코딩 테스트 사이트에서 행렬 거듭제곱을 활용하는 문제 풀이.
    • 선형대수학 교재 또는 온라인 강의 자료.
  • 다음 학습 주제:
    • 고급 수학(선형대수, 이산수학 등)을 활용한 문제 해결 기법 탐구.
    • AI 및 머신러닝에서 행렬 연산의 중요성 및 활용 사례 학습.

참고 자료

  • ChatGPT와의 대화 내용 (특히 모듈러 산술, 분할정복 거듭제곱, 행렬 연산 관련 부분)
  • 백준 1629번 (곱셈) 문제 및 관련 풀이
  • C++ vector를 이용한 행렬 구현 및 연산
  • 단위행렬(Identity Matrix)의 정의 및 역할

오늘의 학습은 단순히 코드를 익히는 것을 넘어, 알고리즘의 근본 원리를 이해하는 경험이었습니다. 앞으로도 꾸준히 학습하며 블로그에 유익한 내용들을 공유하도록 노력하겠습니다.