목재 조각 분할 최적화

문제 개요

길이가 각각 $ L_i $인 $ n $개의 나무 막대가 연속적으로 연결되어 있습니다. 이 중에서 최대 $ m $개의 접합부를 자를 수 있으며, 자른 후 만들어진 조각들 중 가장 긴 조각의 길이가 최소가 되도록 해야 합니다. 또한, 그러한 조합의 경우의 수를 구하고 결과를 $ 10007 $로 나눈 나머지를 출력해야 합니다.

해법 접근

이 문제는 두 가지 부분으로 나뉩니다: 1. 최대 조각 길이의 최솟값을 찾기 위한 이분 탐색 2. 그 값에 대해 가능한 자르는 방법의 수를 동적 프로그래밍으로 계산

이분 탐색을 통한 최소 길이 결정

최소화해야 할 값은 최대 조각 길이이므로, 이 값을 기준으로 이분 탐색을 수행합니다. 가능한 범위는 $ [\max(L_i), \sum L_i] $입니다. 각각의 후보 길이 $ mid $에 대해, 해당 길이로 전체 막대를 분할할 수 있는지 여부를 확인하는 함수를 작성합니다.

bool check(ll mid) {
    ll current = 0;
    int cuts = 0;
    for (int i = 1; i <= n; ++i) {
        if (current + a[i] <= mid)
            current += a[i];
        else {
            current = a[i];
            cuts++;
            if (cuts > m) return false;
        }
    }
    return true;
}

동적 프로그래밍으로 경우의 수 계산

이제 최소 길이 $ len $이 결정되었으므로, 이를 기준으로 가능한 분할 방법의 수를 세어야 합니다. 전형적인 $ O(n^2m) $ DP는 시간 초과가 발생하므로, 효율적인 최적화가 필요합니다.

다음과 같은 아이디어를 사용합니다:

  • $ f[i][j] $: 첫 번째부터 $ j $번째 막대까지 $ i $조각으로 나누는 경우의 수
  • 하지만 메모리와 시간 복잡도를 줄이기 위해, 단일 배열과 전후 고정된 인덱스를 이용한 슬라이딩 윈도우 방식을 적용
  • 각 위치 $ i $에서, $ sum[i] - sum[j] \leq len $을 만족하는 최대 $ j $를 미리 계산하여 $ rem[i] $에 저장
  • 전위 합 배열 $ S $를 사용하여 구간 합을 빠르게 계산
int dp() {
    // rem[i]: sum[i] - sum[rem[i]] ≤ len 를 만족하는 최소 인덱스
    int k = 0;
    for (int i = 1; i <= n; ++i) {
        while (k < i && sum[i] - sum[k] <= len)
            ++k;
        rem[i] = k - 1;
    }

    // 초기 상태: 하나의 조각으로 만들 수 있는 경우
    int res = (sum[n] <= len);
    for (int i = 1; i <= n; ++i) {
        f[i] = (sum[i] <= len);
        S[i] = (S[i-1] + f[i]) % mod;
    }

    // m+1번 분할까지 진행 (최대 m번 자름 → 총 조각 수는 m+1)
    for (int group = 2; group <= m + 1; ++group) {
        for (int i = 1; i <= n; ++i) {
            f[i] = S[i-1];  // 이전까지의 모든 경우
            if (rem[i] >= 0)
                f[i] = (f[i] - S[rem[i]] + mod) % mod;
        }
        // 새로운 전위 합 갱신
        for (int i = 1; i <= n; ++i)
            S[i] = (S[i-1] + f[i]) % mod;
        res = (res + f[n]) % mod;
    }
    return res;
}

결과 출력

이분 탐색을 통해 최소 최대 길이 $ len $을 찾고, $ dp() $ 함수로 경우의 수를 계산하여 출력합니다.

태그: 이분탐색 동적프로그래밍 전위합 슬라이딩윈도우 모듈러연산

8월 9일 08:44에 게시됨