8.9 문제: Quantization ( 문제ID : QUANTIZE, 난이도: 중)

접근법

기존의 방법

quantize(A) = A에 속한 수를 양자화해서 얻을 수 있는 최소 오차 제곱의 합

변형된 방법

quantize(A,U) = U가 지금까지 한 번 이상 사용한 숫자들의 집합일 때 A에 속한 수를 양자화해서 얻을 수
있는 최소 오차 제곱의 합

답의 형태 제한하기

quantize(from, parts) = min [ minError(from, from + size -1) + quantize(from + size, parts -1) ]
    : from번째 이후의 숫자들을 parts개의 묶음으로 묶을 때, 최소 오류 제곱 합을 반환하는 함수

minError(a,b) : a번째 숫자부터 b번째 숫자까지를 하나의 수로 표현했을 때의 최소 오류를 반환하는 함수

minError()의 구현

minError(a,b)에서 해야하는 일은 크게 두 가지 입니다.
1. 주어진 구간을 어떤 수로 표현해야 할지 결정하기
2. 결정한 수m으로 해당 구간을 표현했을 때 오차를 계산하기
1. 주어진 구간 표현

img1

img2

img3

img4

img5

2. 오차 계산하기

img1

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf = 987654321;
// A[]: 양자화해야 할 수열, 정렬한 상태
// pSum[]: A[]의 부분합을 저장한다. pSum[i]는 A[0] .. A[i]의 합
// pSqSum[]: A[]제곱의 부분합을 저장한다. pSqSum[i]는 A[0]^2 .. A[i]^2의 합
int n, m;
int A[101], pSum[101], pSqSum[101];
// A를 정렬하고 각 부분합을 계산한다.
void precalc(){
    sort(A, A+n);
    pSum[0] = A[0];
    pSqSum[0] = A[0] * A[0];
    for(int i = 1; i < n; i++){
        pSum[i] = pSum[i-1] + A[i];
        pSqSum[i] = pSqSum[i-1] + A[i]*A[i];
    }
}
// A[lo]..A[hi] 구간을 하나의 숫자로 표현할 때 최소 오차 합을 계산한다.
int minError(int lo, int hi){
    // 부분합을 이용해 A[lo] ~ A[hi]까지의 합을 구한다.
    int sum = pSum[hi] - (lo == 0 ? 0 : pSum[lo-1]);
    int sqSum = pSqSum[hi] - (lo == 0 ? 0 : pSqSum[lo-1]);
    // 평균을 반올림한 값으로 이 수들을 표현한다.
    int m = int(0.5 + (double)sum / (hi - lo + 1));
    int ret = sqSum -2 * m * sum + m * m * (hi - lo + 1);
    return ret;
}

int cache[101][11];
int quantize(int from, int parts){
    // 기저 사례: 모든 숫자를 다 양자화 했을 때
    if(from == n)return 0;
    // 기저 사례: 숫자는 아직 남았는데 더 묶을 수 없을 때 아주 큰 값을 반환한다.
    if(parts == 0)return inf;
    int& ret = cache[from][parts];
    if(ret != -1)return ret;
    ret = inf;
    // 조각의 길이를 변화시켜 가며 최소치를 찾는다.
    for(int partSize = 1; from + partSize <= n; partSize++)
        ret = min(ret, minError(from, from + partSize -1) + quantize(from + partSize, parts - 1));
    return ret;
}