2629 - 양팔 저울 풀이입니다.
KOI 2001 초등부 2번
2가지 방법이 있네요. 저는 동전 교환 알고리즘(DP) 로 풀었습니다만, 대회 공식 풀이를 보니 휴리스틱을 추가한 재귀로 설명하네요. 구현의 차이일 뿐, 기초적인 발상은 똑같은 것 같습니다. 우선 글 주변이 없는 관계로 의식의 흐름 순서로 설명하는 점 양해를 구하겠습니다.우선 문제에서 주어지는 조건들을 파악했습니다. 주어지는 추는 30개 이하이고, 모두 자연수이며, 같은 무게가 여러 개일 수 있습니다. 그리고 추는 500g 이하네요.
"주어진 추를 사용해서 구슬의 무게(x 라고 합시다.) 를 확인할 수 있는 가" 를 좀 더 수식처럼 바꿔보자면, 각 추를 a0,…an 으로 표현했을 때, x=a0∗(0|1)+⋯+an∗(0|1) 처럼 쓸 수 있을것 같네요. 여기서 (0|1) 은 사용하지않거나/사용하거나를 의미하는 수입니다.
근데 이 수식은 뭔가 부족하네요. 주어진 추로 x를 만들 수 있는가를 묻고 있지만, 구슬(x)와 같은 쪽에 추가 달려있을 경우는 고려되지 않았네요. 다음처럼 바꾸면 해결됩니다.
x=a0∗(−1|0|1)+⋯+an∗(−1|0|1)
방정식의 좌변/우변을 저울의 왼쪽/오른쪽으로 생각하면 좀 읽기 편해질겁니다. -1은 구슬과 같은 쪽에 매달았다는 뜻이 되는거죠.
이제 이 수식에 대한 답을 어떻게 구하는 가가 문제인데, a0,…an을 하나씩 살피면서, 각 ai(i번째 추)에 대해
- 구슬과 같이 올렸을 때(-1)
- 올리지 않았을 때(0)
- 반대쪽에 올렸을 때(+1)
저울의 중심 = |
x | 1
x | 4
x | 1 4
x 1 | 4
x 4 | 1
그렇다면 나올 수 있는 조합의 갯수는 3n 이겠군요! 주어진 추들로 만들 수 있는 모든 x를 구하면 되지 않을까요? 네. 이대로 돌린다면 안됩니다. n 이 30개 이하니까 330. 대충 계산해도 1014 가 넘어가는 대수라 시간초과가 분명합니다.
참고로, 약 108번의 연산을 1000MS로 어림잡아 계산할 수 있습니다. (이것은 정확한 수치가 아니라 컴퓨터의 성능이나 환경에 따라 다릅니다! 시간 측정의 도움을 주는 척도로만 사용하세요.)
그럼 이 문제를 어떻게 해결할까요?
이걸 이용해 모든 경우를 구한다면 이렇게 해결할 수 있습니다.
예시를 그대로 사용하겠습니다. 먼저, 1g짜리 추를 사용해 확인할 수 있는 경우 추의 무게는 (0g+1g/ 0g / 0g-1g) 입니다. 여기서 얻어진 -1g/0g/1g 에는 "1g짜리 추를 사용했다"라는 의미가 들어있습니다. 따라서 여기에 추가적으로 4g에 대해 살핀다면 "1g짜리와 4g짜리를 사용하여"와 같이 의미가 중첩되는 것입니다.
확인할 수 있는 추의 무게를 가진 배열을 C 라고 하겠습니다. 그럼 C에는 처음에는 0g 이 들어있을 것입니다.
C={0}
1g짜리 추를 사용하여 결과를 확인한 후에는 다음과 같습니다.
C={−1,0,1}
각 수들에 대해 4g 을 확인한다면
C={−5,3,−4,4,5,−3}
과 같습니다.
여기서 수를 정렬하지 않은 것은, -5, 3은 -1로부터. -4, 5는 0 으로부터 나왔기 때문입니다. 즉, -5 = -1-4(1g과 4g을 구슬과 같은 쪽에) 이고 3 = -1+4(1g은 구슬과 같은 쪽, 4g은 반대 쪽)를 의미합니다.
이 문제에서 사실 우리는 음수를 고려할 필요가 없습니다. 왜냐하면 (위에서 표현한 대로 저울을 표현하자면) x 1 | 4 와 4 | x 1 는 같기 때문입니다.
이런 식으로 점차 추의 무게를 키워가며 확인한다면, 모든 추의 무게를 확인할 수 있습니다.
나올 수 있는 최대 무게는 500g의 추가 30개인 15000g입니다.
요약
적절한 가지치기 혹은 동전 교환 알고리즘을 통해 모든 경우의 수를 구한다.
더보기
#include <cstdio>
#include <algorithm>
inline int abs(int n){return n<0?-n:n;}
int main(){
int i, j, n, a[30];
scanf("%d", &n);
for(i=0; i<n; ++i) scanf("%d", &a[i]);
// std::sort(a, a+n);
bool can[15001]={}, temp[15001]={};
temp[0] = can[0] = true;
for(i=0; i<n; ++i){
for(j=0; j<=15000; ++j){
if(can[j] == true){
int left=abs(a[i]-j), right=abs(a[i]+j);
if(left >= 0 && left <= 15000) temp[left] = true;
if(right >= 0 && right <= 15000) temp[right] = true;
temp[ a[i] ] = true;
}
}
for(j=0; j<=15000; ++j) can[j] = temp[j];
}
int m, p;
scanf("%d", &m);
while(m--){
scanf("%d", &p);
printf( p <= 15000 && can[p] ? "Y ":"N " );
}
return 0;
}
댓글 없음:
댓글 쓰기