Quantization
어떻게 풀까?
위 문제는 주어진 배열만으로 양자화를 진행하기 보다는 데이터를 정렬해서 보는 것이 굉장히 편합니다.
양자화라는 것은 결국 비슷한 숫자를 특정한 부분에 매핑시키는 것이기 때문에, 데이터를 정렬하면 결국 양자화된 값도 정렬된 상태로 존재하게 됩니다.
따라서, 만약 데이터를 정렬한다면, 이 문제는 해당 구간을 어떻게 s개로 나누고, 각 부분에 1~1000 중에서 어떤 숫자를 넣을지 완전 탐색을 하는 문제로 바꿀 수 있는 것입니다!
큰 틀을 잡았으니
이번에는, s개의 부분으로 나눴다고 가정해서, 각 s에는 어떤 수를 넣어야 최소의 값이 나올지 생각해봅시다.
오차 제곱의 합이기 때문에 아래와 같은 수식으로 나타낼 수 있습니다.
위 식은 m에대한 2차 함수로, 2차항의 계수 (b - a + 1)이 양수이기 때문에 미분을 통해서 그 최솟값을 구할 수 있습니다!
2 차 함수가 아래로 볼록일 때, 함수의 최솟값은 미분 값이 0 에 가까운 곳이기 때문입니다.
식을 미분하면
이고, 이를 m에 대해서 정리하면,
을 얻을 수 있습니다. 계산하고 보니, 결국 구간의 평균값임을 알 수 있습니다!
A[i]까지 배열의 합을 O(1) 만에 해결하기 위해 부분합 공식을 이용합시다.
부분합을 이용해서 어떤 배열 pSum[i]라는 배열에 A[0] ~ A[i] 까지의 합을 저장하는 배열을 만든다면,
는 A[b] - A[a-1]을 통해 구할 수 있습니다. 이를 이용하면, 부분 문제의 오차 제곱도 O(1) 시간에 구할 수 있습니다.
참고로, pSum에서 A[0]~A[0]까지의 합을 구하기 위해서는 pSum[0] - pSum[-1]을 계산해야하기 때문에,
A의 인덱스를 1 부터 시작하게 하고, pSum[0] = 0 으로 둔다면 훨씬 쉽고 일반적으로 구간합을 구할 수 있게 됩니다!
위 : A[i], 아래 : pSum[i] = (A[0]에서 A[i+1]까지의 합)
위의 식을 보면 pSum[3] - pSum[1] = 6 - 3 = 3 이고, 이는 A[3] + A[2]의 값임을 알 수 있습니다.
이제 다시 s개의 부분을 나눠서 어떻게 하면 좋을지 생각해봅시다.
우선, 앞에서 어떠한 경로로 왔던지 간에, 한 부분 집합을 몇개로 양자화 하는지 같다면, 그 결과 값은 같습니다. (최적 부분 구조)
예를 들면, {1,2,3,5,9,18,22} 가 주어지면, {3,5,9,18,22} 로 오기전에 양자화 된 숫자만 같다면, {1,2}를 어떻게 양자화 하던지는 상관이 없습니다.
{3,5,9,18,22} 을 2 개의 서로 다른 숫자로 양자화 한다는 조건만 있다면, 앞의 부분은 상관이 없다는 것 입니다!
하지만, {3,5,9,18,22}을 3 개의 서로 다른 숫자로 양자화 하거나, 1 개의 숫자로만 양자화 한다면, 최적 오차의 값이 달라질 것 입니다.
따라서, 메모이제이션에는 해당 부분집합이 어디서 부터인지 시작하는지에 대한 정보와, 몇 개의 부분으로 나눌 수 있는지에 대한 정보 2개를 이용하도록 합니다.
그렇다면 이제 아래의 코드를 통해 어떻게 이 생각을 코드로 작성하는지 알아봅시다!
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
#pragma once
#include<cstdio>
#include<cstring>
// Quantization
// 알고스팟
// https://algospot.com/judge/problem/read/QUANTIZE
int nums[101] = { 0 }, n, s;
int temp[101], memoi[11][101];
const int INF = 987654321;
int pSum[101] = { 0 }, p2Sum[101] = { 0 };
int min(int i1, int i2)
{
return i1 < i2 ? i1 : i2;
}
void merge(int left, int m, int right)
{
int l = left, r = m + 1, k = left;
while (l <= m && r <= right)
{
if (nums[l] < nums[r])
{
temp[k++] = nums[l++];
}
else
{
temp[k++] = nums[r++];
}
}
while (l <= m) temp[k++] = nums[l++];
while (r <= right) temp[k++] = nums[r++];
for (int i = left; i <= right; i++)
{
nums[i] = temp[i];
}
}
void mergeSort(int left, int right)
{
if (left < right)
{
int m = (left + right) / 2;
mergeSort(left, m);
mergeSort(m + 1, right);
merge(left, m, right);
}
}
int sMin(int left, int right)
{
int m = (int)((double(pSum[right] - pSum[left - 1])) / (right - left + 1) + 0.5);
return p2Sum[right] - p2Sum[left - 1] - 2 * (pSum[right] - pSum[left - 1]) * m + m * m * (right - left + 1);
}
int getMin2(int cnt, int idx)
{
int &ret = memoi[cnt][idx];
if (ret != -1)
{
return ret;
}
if (idx == n)
{
return ret = 0;
}
if (cnt == 1)
{
return ret = sMin(idx, n);
}
ret = INF;
for (int next = idx + 1; next <= n; next++)
{
ret = min(ret, sMin(idx, next - 1) + getMin2(cnt - 1, next));
}
return ret;
}
void init()
{
scanf("%d %d\n", &n, &s);
for (int i = 1; i <= n; i++)
{
scanf("%d \n", &nums[i]);
}
mergeSort(1, n);
}
int main()
{
int t;
scanf("%d\n", &t);
while (t--)
{
init();
int res = INF;
memset(memoi, -1, sizeof(memoi));
memset(pSum, 0, sizeof(pSum));
memset(p2Sum, 0, sizeof(p2Sum));
for (int i = 1; i <= n; i++)
{
pSum[i] += pSum[i - 1] + nums[i];
p2Sum[i] += p2Sum[i - 1] + nums[i] * nums[i];
}
res = getMin2(s, 1);
printf("%d\n", res);
}
return 0;
}
|
cs |
여담
생각보다 어려웠습니다! 특히, 부분합에서 평균을 구하면 손쉽게 어떤 수로 양자화해야지 안다는 생각을 못해서 너무 너무 시간이 오래걸렸습니다. 저는 처음에... 1부터 1천까지 다 넣으면서 최소의 값을 구했었습니다... 그래서 풀리긴 했지만 시간이 굉장히 오래걸렸습니다.
해당 방법의 시간 복잡도를 구해봅시다. 시간 복잡도는 부분 문제의 개수가 ns개 이고, 부분 집합에 반복문이 하나 있기 때문에
의 시간이 걸립니다.
'공부 > 알고리즘 문제풀이' 카테고리의 다른 글
[알고스팟] 삼각형 위의 최대 경로 개수 세기 (0) | 2018.06.16 |
---|---|
[알고스팟] 타일링 방법의 수 세기 (0) | 2018.06.16 |
[알고스팟] 원주율 외우기 (0) | 2018.06.15 |
[알고스팟] 합친 LIS (0) | 2018.06.14 |
[알고스팟] 최장 증가 부분 수열 LIS (longest increasing subsequence) (0) | 2018.06.14 |