-
알고스팟 문제 풀이 PICNIC24년 11월 이전/레거시-알고스팟-알고리즘 문제 해결 전략 2019. 9. 27. 11:10반응형
책 "알고리즘 문제 해결 전략"에 나오는 algospot 문제 "PICNIC"에 대한 풀이입니다.
목차
- 문제 해결 전략
- 코드 전문
문제 해결 전략
PICNIC 문제 URL은 다음과 같습니다. 급하신 분들은 바로, 코드 전문으로 넘어가주세요!
제가 이 문제를 해결하기 위해서 체택한 방법은 "무식하게 풀기"입니다. 먼저 이해를 위해서 3번째 입력에 대해서, 살펴보도록 하겠습니다.
n = 6, m = 10
set = (0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (3, 5), (4, 5)이것을 그림으로 나타내 봅시다.
먼저 (0, 1) 세트를 골랐을 때, 다음으로 고를 수 있는 친구의 셋은 (2, 3), (2, 4), (3, 4), (3, 5), (4, 5) 입니다.
그 다음으로 (2, 3)을 고르면, (4, 5) 세트를 고를 수 있기 때문에, 모든 학생들을 친구와 짝을 맺게 해줄 수 있습니다.
만약 (2, 3)을 고르지 않았을 땐 (2, 4)를 고를 수 있습니다. 역시 (3, 5)를 고를 수 있기 때문에, 짝을 지어줄 수 있습니다.
근데, 세트 (2, 3), (2, 4)을 고르지 않았다고 가정해봅시다. (3, 4)를 고르게 되면, 2번과 5번은 친구가 아니기 때문에, 짝을 다 지어줄 수 없습니다.
(3, 5), (4, 5)는 각각 (2, 3), (2, 4)를 골랐을 때와 같은 상황이므로, 같은 경우의 수라고 생각하시면 됩니다. 위 과정을 (0, 1) 세트를 골르지 않았을 때로 반복해서 친구끼리 짝을 지은 전체 경우의 수를 반환하면 됩니다.
결국 이 문제를 해결하기 위해서는 세트 목록 중에서 한 세트에 대해서 선택한 것과 안 선택한 것의 경우의 수를 따져보면 됩니다.
또한, 이 문제를 풀 때는 반복문 보다는 재귀 호출문이 조금 더 편하다고 생각합니다. 왜냐하면, 반복문이라고 생각해봅시다. 이 때, 선택을 했을 경우의 수와 선택을 안했을 때 경우의 수를 쉽사리 생각할 수 있나요? 저의 경우, 반복문으로 해결하는게 쉽게 생각이 나지 않았습니다.
반면, 재귀 호출 방식을 선택했을 때는, 해당 인덱스에서 선택하느냐 안하느냐로 코드로 쉽게 나타낼 수 있기 때문에, 이 방법이 조금 더 좋다고 생각합니다. 다음 코드처럼 말이죠.
int solve(int m, int idx, vector<bool> isCheck, const vector< vector<int>>& friendSet) { // 재귀 탈출 조건1: 모든 짝이 지어진 경우 1을 추가합니다. if (IsAllCheck(isCheck)) { return 1; } // 재귀 탈출 조건2: 친구 목록을 다 순회해도, 짝이 지어지지 않은 경우입니다. if (m <= idx) { return 0; } int ret = 0; auto pair = friendSet[idx]; // 친구 set을 선택하지 않았을 때 경우의 수입니다. ret += solve(m, idx + 1, isCheck, friendSet); // 만약 이 친구 set의 인원 중 누구라도 이전에 선택됐다면, 이 set은 선택 될 수 없습니다. // 따라서, 선택하지 않았을 때의 경우의 수만을 반환합니다. if (isCheck[pair[0]] || isCheck[pair[1]]) { return ret; } // 친구 set을 선택했을 경우입니다. isCheck[pair[0]] = true; isCheck[pair[1]] = true; ret += solve(m, idx + 1, isCheck, friendSet); // 친구 set을 선택했을 때와 안 선택했을 때의 경우의 수를 합한 값을 반환합니다. return ret; }
주석을 살펴보시면, 무엇을 말하고 있는지 금방 아실 수 있을 겁니다.
코드 전문
다음은 알고리즘 풀이 코드 전문입니다. main은 입력에 대해서 예외 처리를 한 후 solution 함수를 부릅니다. solution 함수는 입력 n에 대해서, 체크 여부를 나타내는 리스트를 만들고, 그것을 가지고 solve 함수를 호출한 그 결과를 반환합니다.
#include <iostream> #include <vector> using namespace std; const int MAX_N = 10; const int MAX_M = MAX_N * (MAX_N-1) / 2; bool IsAllCheck(const vector<bool> &isCheck) { for (int i=0; i<isCheck.size(); i++) { if (!isCheck[i]) { return false; } } return true; } int solve(int m, int idx, vector<bool> isCheck, const vector< vector<int>>& friendSet) { if (IsAllCheck(isCheck)) { return 1; } if (m <= idx) { return 0; } int ret = 0; auto pair = friendSet[idx]; ret += solve(m, idx + 1, isCheck, friendSet); if (isCheck[pair[0]] || isCheck[pair[1]]) { return ret; } isCheck[pair[0]] = true; isCheck[pair[1]] = true; ret += solve(m, idx + 1, isCheck, friendSet); return ret; } int solution(int n, int m, vector< vector<int>> friendSet) { auto isCheck = vector<bool>(n, false); return solve(m, 0, isCheck, friendSet); } int main() { int c; cin >> c; if ( !(0 < c && c <= 50) ) { cout << "fail" << endl; return 0; } for (int i=0; i<c; i++) { int n, m; cin >> n >> m; if ( !(2 <= n && n <= MAX_N) ) { cout << "fail" << endl; return 0; } if ( !(0 <= m && m <= MAX_M) ) { cout << "fail" << endl; return 0; } vector< vector<int>> friendSet; for (int j=0; j<m; j++) { int a, b; cin >> a >> b; friendSet.push_back({a, b}); } int result = solution(n, m, friendSet); cout << result << endl; } return 0; }
다음 코드를 제출하면, 40ms 이내로 무난하게 통과하는 것을 확인할 수 있습니다. 근데 0ms로 통과하는 사람들이 있는 것을 보면 "동적 계획법"이나 다른 알고리즘 패러다임을 이용하여 최적화하는 방식이 있을 것으로 추정됩니다. 이는 솔루션을 찾게 되면, 그 때 재 업로드 하도록 하겠습니다.
728x90'레거시 > 레거시-알고스팟-알고리즘 문제 해결 전략' 카테고리의 다른 글
알고스팟 문제 풀이 BOGGLE (0) 2019.10.08 알고스팟 문제 풀이 BOARDCOVER (0) 2019.10.06 [알고스팟] 외계 신호 분석(ITES) (0) 2019.01.23 [알고스팟] 조세푸스 문제(JOSEPHUS) (0) 2019.01.23 [알고스팟] 짝이 맞지 않은 괄호(BRACKETS2) (0) 2019.01.23