문제
https://www.acmicpc.net/problem/14889
풀이
문제가 조금 복잡할 수도 있고, 주의할 점도 있다. 예시로 {1, 2, 3, 4, 5, 6} 이라는 멤버에서 {1, 2, 3} 과 {4, 5, 6} 팀을 뽑았다고 생각해보자.
- n개의 팀에서 n / 2씩 분리하는 것을 생각 해줘야 한다. 이것은 [14888. 연산자 끼워넣기] 에서 사용했던 방법처럼 DFS로 경우의 수를 모두 구해줄 수 있다. 주의할 점은, {1, 2, 3} {4, 5, 6} 과 {2, 1, 3} {5, 4, 6}은 같은 그룹이라는 것이다. 따라서 이런 중복되는 그룹을 배제하고 묶는 경우를 구해줘야 한다. 이 점을 주의하자. (안 하면 시간초과가 날 것이다.)
- 분리된 팀의 능력치는 첫번째 팀은 S12 + S21 + S13 + S31 + S23 + S32이 나올테고, 두번째 팀은 S45 + S54 + S46 + S64 + S56 + S65 가 나올 것이다. (물론 S11, S22, S33, S44, S55, S66 같은 경우는 0이므로 더해줘도 상관은 없다.) 따라서 team1, team2의 멤버들을 2개의 벡터로 나눠준 후, 각각 벡터의 index가 0일 때, 0 ~ vector.size까지의 값을 다 더해주고, index가 2일 때, index가 3일 때, ... 를 전부 구해준다. 능력치는 p[21][21]안에 있으므로 p[v[i]][v[j]]와 같은 식이 될 것이다.
- 능력치의 차가 최소가 된다고 했는데, 이 때 음수값은 나오지 않으므로 team1 과 team2 중에서 큰 값에서 작은 값을 빼줘야한다. 또는 abs를 이용해서 절대값을 취해줘도 무방하다. abs는 <stdlib.h> 안에 정의 되어 있다.
계산이 어떤 식으로 나오는지 궁금하면 주석에 달린 출력을 주석 해제하고 확인해보자.
// // 14889.cpp // BJ // // Created by 신기열 on 24/10/2019. // Copyright © 2019 신기열. All rights reserved. // #include <stdio.h> #include <iostream> #include <vector> using namespace std; int n; int visited[21]; int p[21][21]; int answer = 100000000; // 각 팀의 능력치를 구하기 위한 함수 void calc(vector<int> v){ // team1이 v 안에 있고 v 안에 없는 멤버들은 nv 안에 넣는다 vector<int> nv; int team1 = 0, team2 = 0; // team1을 제외한 나머지 멤버를 team2에 넣는다. for(int i = 1; i <= n; i++){ bool isExist = false; for(int j = 0; j < v.size(); j++){ if(v[j] == i){ isExist = true; break; } } if(!isExist) nv.push_back(i); } // cout << "{ "; // for(int i = 0; i < v.size(); i++){ // cout << v[i] << " "; // } // cout << "}"; // // cout << " { "; // for(int i = 0; i < v.size(); i++){ // cout << nv[i] << " "; // } // cout << "} answer : "; // team1의 능력치를 구한다. team1의 첫번째 멤버 당(v[i]) 두번째 멤버, 세번째 멤버, 네번째 멤버... 의 능력치를 차례로 더해주고, // 두번째, 세번째, 네번째, ... 에도 똑같이 계산해준다. for(int i = 0; i < v.size(); i++){ for(int j = 0; j < v.size(); j++){ team1 = team1 + p[v[i]][v[j]]; } } // team2의 능력치를 구한다. for(int i = 0; i < nv.size(); i++){ for(int j = 0; j < nv.size(); j++){ team2 = team2 + p[nv[i]][nv[j]]; } } // cout << "max(" << max(team1, team2) << ") - min(" << min(team1, team2) << ") = " << max(team1, team2) - min(team1, team2) << '\n'; // max(team1, team2) - min(team1, team2) = |team1 - team2| 의 최솟값을 구해준다. answer = min(answer, max(team1, team2) - min(team1, team2)); } void DFS(vector<int> v, int tmp[21]){ int tvisited[21]; // 한 팀에 들어갈 수 있는 멤버의 수는 n / 2이므로 멤버 벡터에 n / 2개의 원소가 들어가있으면 계산하고 끝낸다. if(v.size() == n / 2){ calc(v); return; } // tv, tvisited 초기화 vector<int> tv(v.size()); for(int i = 0; i < v.size(); i++){ tv[i] = v[i]; } for(int i = 1; i <= n; i++){ tvisited[i] = tmp[i]; } // 특정 멤버에 대해 탐색을 한 적이 없으면, 즉 어떤 팀에 포함되어 있지 않으면 team1에 넣어주고 DFS를 돌려준다. DFS가 끝나면 다시 team1에서 // 빼준다. 이 때 visited를 초기화하지 않는 이유는 만약 1번 멤버 탐색이 끝났으면 1번 멤버에 대한 모든 케이스 탐색이 끝난 것이므로 다시 // 1번 멤버를 포함해서 케이스를 구해줄 필요가 없다. for(int i = 1; i <= n; i++){ if(tvisited[i] == 0){ tvisited[i] = 1; tv.push_back(i); DFS(tv, tvisited); tv.pop_back(); } } } int main(){ cin >> n; for(int i = 1; i <= n; i++){ for(int j = 1; j <= n; j++){ cin >> p[i][j]; } } vector<int> v; DFS(v, visited); cout << answer; return 0; }
댓글
댓글 쓰기