ddongyeonn 2020. 3. 6. 14:59

문제

Quantization(양자화) 과정은, 더 넓은 범위를 갖는 값들을 작은 범위를 갖는 값들로 근사해 표현함으로써 자료를 손실 압축하는 과정을 말한다. 예를 들어 1000 이하의 자연수들로 구성된 수열을 s가지의 자연수만을 사용하도록 양자화하려고 한다. 1 2 3 4 5 6 7 8 9 10을 두 가지 숫자만을 써서 표현하려면 3 3 3 3 3 7 7 7 7 7 또는 1 1 1 1 1 10 10 10 10 10처럼 표현할 수 있다. 이중 각 숫자별 오차 제곱의 합의 최소치를 구하는 프로그램을 작성하시오.

입력

입력의 첫 줄에는 테스트 케이스의 수 C(1~50)이 주어짐.

각 테스트 케이스의 첫 줄에는 수열의 길이 n(1~100), 사용할 숫자의 수 s(1~10)가 주어진다.

그 다음 줄에 n개의 정수로 수열의 숫자들이 주어지며, 수열의 모든 수는 1000 이하의 자연수이다.

예제 입력 및 출력

입력

2

10 3

3 3 3 1 2 3 2 2 2 1

9 3

1 744 755 4 897 902 890 6 777

 

출력

0

651

소스코드

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int Case;
// cache[pivot][length] = pivot에서 길이 length만큼 부분수열의 오차제곱 최솟값을 저장
int cache[100][100]; 

class Quantization {
private:
	int arrlength; //수열의 길이
	int usenum; // 사용할 숫자의 수
	vector<int> sequence; //수열
public:
	void setQuant() {
		int tmp;
		cin >> arrlength >> usenum;
		for (int i = 0; i < arrlength; i++) {
			cin >> tmp;
			sequence.push_back(tmp);
		}
	}
	//버블정렬
	void bubblesort() {
		for (int i = 1; i < arrlength; i++) {
			for (int j = 0; j < arrlength - i; j++) {
				if (sequence[j] > sequence[j + 1])
					swap(sequence[j], sequence[j + 1]);
			}
		}
	}
	//오차제곱 최솟값을 찾는 함수
	int findMinError(int pivot, int length, int part) {
		int average = 0;
		int sum = 0;

		//main함수에서 처음 호출할때 피벗이 -1이므로 예외처리
		if (pivot >= 0) {

			//기저사례- 현위치에서 해당길이만큼 수열의 오차제곱합이 이미 계산된경우 바로 반환
			if (cache[pivot][length] != -1)
				return cache[pivot][length];

			//부분수열의 총합
			sum = sequence.at(pivot);
			for (int i = 1; i < length; i++)
				sum += sequence.at(pivot + i);
			average = round(sum / (double)length); //평균값(반올림한 값)
			//부분수열의 오차제곱합
			sum = 0;
			for (int i = 0; i < length; i++) {
				sum += (sequence.at(pivot + i) - average) * (sequence.at(pivot + i) - average);
			}
			cache[pivot][length] = sum;
		}

		//마지막 부분수열일경우 바로 반환
		if (part == usenum)
			return cache[pivot][length];

		//마지막 부분수열이 아닐경우 현재 pivot을 기준으로 가능한 모든 길이의 부분수열을 만든다
		for (int i = 1; i <= arrlength - pivot - length - usenum + part + 1; i++) {
			//다음번 부분수열이 마지막 부분수열일경우 길이는 항상 동일
			if (part + 1 == usenum) {
				cache[pivot][length] = sum + findMinError(pivot + length, arrlength - pivot - length, part + 1);
				break;
			}
			//다음번 부분수열이 마지막이 아니라면 길이는 달라짐 -길이별 최소값을 찾는다
			else {
				if (i == 1)
					cache[pivot][length] = sum + findMinError(pivot + length, 1, part + 1); //길이 1일때 오차제곱 최솟값 바로 저장
				else
					//길이 2 이상일때부터는 최솟값을 cache에 저장
					cache[pivot][length] = min(cache[pivot][length], sum + findMinError(pivot + length, i, part + 1)); 
			}
		}
		//결과 최솟값 반환
		return cache[pivot][length];
	}
};

Quantization* quant;

int main(void) {
	cin >> Case;

	quant = new Quantization[Case];

	for (int i = 0; i < Case; i++)
		quant[i].setQuant();

	for (int i = 0; i < Case; i++) {
		memset(cache, -1, sizeof(cache));
		quant[i].bubblesort(); //입력받은 수열을 정렬
		cout << quant[i].findMinError(-1, 1, 0) << endl; //오차 최솟값 구하는 함수 호출
	}
}

문제풀이

1. cache값 설정

   - cache[pivot][length]: 수열의 pivot번째 수에서 길이가 length인 부분 수열의 오차 제곱 값을 저장

 

2. 기저사례

   - cache값이 이미 계산되었다면 부분 수열을 나눌 필요 없이 바로 반환.

 

3. 함수 탈출 조건

   - 사용할 숫자 usenum이 주어지면 총 usenum개의 부분수열을 생성해야 한다.

   - part값을 인자로 전달하여 part==usenum이 되면 마지막 부분 수열로 더 이상 부분 수열을 나눌 필요 없이 오차 제곱 값을 계산하여       바로 반환한다.

 

4. 점화식

   - 현재 부분 수열이 마지막 부분수열이 아니라면 다음번 부분 수열의 길이가 1~ (남은수열 길이-남은사용할 문자)만큼 생성할 수 있다.

   - 각각의 재귀 함수를 호출하여 반환 값 중 최솟값을 현재 cache값에 저장하여 반환한다.