diff --git a/1130 - MST/1368.cpp b/1130 - MST/1368.cpp new file mode 100644 index 0000000..310ddf7 --- /dev/null +++ b/1130 - MST/1368.cpp @@ -0,0 +1,74 @@ +// +// Created by 반예원 on 2021/12/06. +// + +#include +#include +#include + +using namespace std; +const int INF = 1e5 + 1; + +int prim(int size, int start, vector> &graph) { + //프림 알고리즘 + int sum = 0; //합계 0 + vector dist(size, INF); //각 논까지의 비용 + vector visited(size, false); //논 방문 여부 + priority_queue, vector>, greater<>> pq; + //우선순위 큐 사용 + //초기화 + dist[start] = 0; //처음 start 값 0으로 초기화 + pq.push({0, start}); +// pq 우선순위큐에 값 저장 + while (!pq.empty()) { //비기 ㄱ전 까지 반복하기 + int cost = pq.top().first; //간선 가중치 + int cur = pq.top().second; //현재 논 + pq.pop(); //가장윗값 빼기 + + if (visited[cur]) //이미 확인했던 정점 + continue; //다시 진행 + sum += cost; //MST 간선 가중치 총합 + visited[cur] = true; //방문 처리 + + for (int i = 0; i < size; i++) { //사이즈만큼 반복하여 + if (!visited[i] && graph[cur][i] < dist[i]) { //미방문 정점이면서 더 짧은 간선을 통해 갈 수 있다면 + dist[i] = graph[cur][i]; //그래프의 값 각 논까지의 비용벡터에 저장 + pq.push({dist[i], i}); //i와 dist값 저장 + } + } + } + return sum; //비용 반환 +} + +/** + * 각 논들 사이의 간선도 고려하고, 우물을 파는 경우도 고려? -> 복잡 + * 논에 추가로 모든 우물과 연결되는 수원이 있다고 가정! + * ->직접 논에 우물을 파는 경우는 수원과 각 논 사이의 간선 가중치라고 할 수 있음 + * + * 0 2 2 2 5 + * 2 0 3 3 4 + * 2 3 0 4 4 + * 2 3 4 0 3 + * 5 4 4 3 0 + * + * 인덱스 0 ~ n-1은 논, 인덱스 n은 수원 + * 1개 이상의 논은 반드시 직접 우물을 파야 하므로 수원(n)에서 시작하는 프림 알고리즘 + */ +int main() { + int n, w; //논의 수와 우물 팔때 비는 비용 + + cin >> n; //논의 수 입력 받기 + vector> graph(n + 1, vector(n + 1, 0)); + // 그래프 2차벡터로 초기화 + for (int i = 0; i < n; i++) { //수원으로부터 물을 끌어오는 비용 + cin >> w; // 비용 입력받기 + graph[i][n] = graph[n][i] = w; // 해당 그래프에 비용과 n,i서로 저장 + } + + for (int i = 0; i < n; i++) { //논의수만큼 반복하여 + for (int j = 0; j < n; j++) //이중 반복 + cin >> graph[i][j]; //논들 사이에서 물을 끌어오는 비용 + } + + cout << prim(n + 1, n, graph); //수원에서 시작하는 프림 알고리즘 +} \ No newline at end of file diff --git a/1130 - MST/16202.cpp b/1130 - MST/16202.cpp new file mode 100644 index 0000000..eb6dc46 --- /dev/null +++ b/1130 - MST/16202.cpp @@ -0,0 +1,92 @@ +// +// Created by 반예원 on 2021/12/07. +// + + +#include +#include +#include +#include + +using namespace std; +typedef tuple tp; + +vector parent; + +//Find 연산 +int findParent(int node) { + if (parent[node] < 0) //값이 음수면 루트 정점 + return node; //nodq 반환 + return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기 +} + +//Union 연산 +bool unionInput(int x, int y) { + int xp = findParent(x); //부모 찾고 선언 + int yp = findParent(y);//부모 찾고 선언 + + if (xp == yp) //같은 집합에 있다면 유니온 할 수 없음 + return false; //false로 반환 + if (parent[xp] < parent[yp]) { //새로운 루트 xp + parent[xp] += parent[yp]; // 노드 개수갱신 + parent[yp] = xp; //다시 xp 를 갱신 하여 부모로 + } else { //새로운 루트 yp + parent[yp] += parent[xp]; // 노드 개수갱신 + parent[xp] = yp;//다시 yp 를 갱신 하여 부모로 + } + return true; //true로 반환 +} + +int kruskal(int n, int idx, vector &edges) { //인자 받기 + int cnt = 0, sum = 0; //카운트와 합계 선언 + for (int i = idx; i < edges.size(); i++) { //벡터사이즈만큼 반복 + if (cnt == n - 1) //n-1개의 간선을 모두 연결함 + break; + int dist = get<0>(edges[i]); // 튜플로부터 값 가져오기 + int x = get<1>(edges[i]);// 튜플로부터 값 가져오기 + int y = get<2>(edges[i]);// 튜플로부터 값 가져오기 + + if (unionInput(x, y)) { //유니온 후 true시 + cnt++; //개수 증가 + sum += dist; //거리 더하여 ㄱ갱신 + } + } + if (cnt < n - 1) //mst를 만들 수 없음 + return 0; //0으로 반환 + return sum; //총 거리 반환 +} + +/** + * MST 알고리즘을 여러 번 실행해도 될까? + * 1. 크루스칼 알고리즘의 시간 복잡도는 O(ElogE) + * 이는 오직 간선을 정렬하는 연산의 시간 복잡도! + * 즉, 모든 간선을 한 번 정렬해서 저장해두면 이후 몇 번의 알고리즘을 수행하여도 연산 시간에 큰 영향이 없음 + * 2. 간선 재사용을 위해 우선순위 큐가 아닌 벡터에 저장하고 크루스칼 알고리즘 k번 실행 + * 3. 매번 크루스칼을 수행할 때마다 제일 먼저 추가한 간선을 제외함 + * -> 제외될 간선은 배열의 0번째 간선부터 1, 2, 3번째 간선으로 순차적 제외 + * 4. 만약 한 번 MST를 만들 수 없다는게 확인됐다면 이후에도 MST를 만들 수 없으므로 flag 변수로 불필요한 연산 절약 + */ +int main() { + int n, m, k, x, y; + //정점 개수 간선 개수 턴 수 두정점 번호 + + cin >> n >> m >> k; // 정점 개수, 간선 개수, 턴수 입력 받기 + vector edges; //재사용할거라 우선순위 큐가 아닌 벡터에 저장 + for (int i = 1; i <= m; i++) { // 간선 수 만큼 반복하여 + cin >> x >> y; //두 정점 입력 받기 + edges.emplace_back(i, x, y); // 벡터의원소 끝에 값추가 + } + + bool flag = false; //bool 선언 + for (int i = 0; i < k; i++) { + if (flag) { //더이상 mst를 만들 수 없음 + cout << 0 << ' '; //0출력 + continue; + } + parent.assign(n + 1, -1); //초기화 + int ans = kruskal(n, i, edges); //크루스칼 알고리즘 사용 + if (ans == 0) //반환 닶이 0이라면 + flag = true;//flag값 true로 변경 + cout << ans << ' '; + } +} \ No newline at end of file diff --git a/1130 - MST/1713.cpp b/1130 - MST/1713.cpp new file mode 100644 index 0000000..87a06fe --- /dev/null +++ b/1130 - MST/1713.cpp @@ -0,0 +1,58 @@ +// +// Created by 반예원 on 2021/12/06. +// +#include +#include +#include + +using namespace std; +typedef pair ci; + +map::iterator delCandidate(map &candidate) { + auto del = candidate.begin(); //처음 후보를 삭제한다 가정 + int cnt = candidate.begin()->second.first; //처음 후보의 추천 횟수 + int t = candidate.begin()->second.second; //처음 후보의 게시 시간 + for (auto iter = ++candidate.begin(); iter != candidate.end(); iter++) { + //반복 하여 후보 벡터 돌기 + int cur_cnt = iter->second.first; //추천횟수값 저장 + int cur_t = iter->second.second;//게시시간 저장 + if (cur_cnt < cnt) { //추천 횟수가 가장 작은 후보 찾기 + cnt = cur_cnt; //작다면 추천횟수 갱신 + t = cur_t; //게시시간 갱신 + del = iter; //다시 다음 후보로 + } else if (cur_cnt == cnt && cur_t < t) { //추천 횟수가 가장 작은 후보가 여러명이라면, 게시 시간이 오래된 후보 찾기 + t = cur_t; //게시시간 갱신 + del = iter; //다시 다음 후보로 + } + } + return del; //삭제할 후보 반환 +} + +/** + * 1. 비어있는 사진틀이 없는 경우, 가장 추천수가 작은 학생 중 게시 시간이 오래된 학생을 삭제 + * 2. 후보 학생을 바로 찾기 위해 본 풀이는 map 컨테이너를 사용해 구현 + * + * !주의! 게시 시간 정보 저장 시, 후보로 올라간 가장 첫 시간을 저장. 이미 후보에 있는데 게시 시간이 갱신되지 않도록 주의. + */ + +int main() { + int n, m, input; + + //입력 & 연산 + cin >> n >> m; //사진 클과 전체 학생 수 입력 받기 + map candidate; //first: 후보 학생, second: {추천 횟수, 게시 시간} + for (int i = 0; i < m; i++) { //전체 학생 만큼 반복 + cin >> input; // 추천 값 입력 받기 + if (candidate.size() == n && candidate.find(input) == candidate.end()) //비어있는 사진틀이 없는 경우 + candidate.erase(delCandidate(candidate)); + // 가장 추천수 적은 학생 중 게시시간이 오래된 학생 삭제 + + if (candidate.find(input) == candidate.end()) //첫 게시라면 + candidate[input].second = i; //해당 후보의 {추천 횟수, 게시 시간}에 i값 저장 + candidate[input].first++; //추천 횟수 증가 + } + + //출력 + for (auto iter = candidate.begin(); iter != candidate.end(); iter++) + cout << iter->first << ' '; +} \ No newline at end of file diff --git a/1207 - topological sort/2252.cpp b/1207 - topological sort/2252.cpp new file mode 100644 index 0000000..068c141 --- /dev/null +++ b/1207 - topological sort/2252.cpp @@ -0,0 +1,52 @@ +// +// Created by 반예원 on 2021/12/21. +// + +//라이브코딩 +#include +#include +#include +using namespace std; + +vector topologicalSort(int n, vector &indegree,vector> &graph){ + vector result; + queue q; + + for(int i=1;i<=n;i++){ + if(!indegree[i]) + q.push(i); + } + while(!q.empty()){ + int node = q.front(); + q.pop(); + + result.push_back(node); + for(int i=0;i< graph[node].size();i++){ + int next_node = graph[node][i]; + indegree[next_node]--; + if(!indegree[next_node]) + q.push(next_node); + } + } + return result; + +} +int main(){ + int n,m,a,b; + + cin >> n >> m; + vector indegree(n+1,0); + vector> graph(n+1,vector(0)); + while(m--){ + cin >> a >> b; //a result = topologicalSort(n,indegree,graph); + for(int i=0;i +#include +#include +#include +#include + +using namespace std; + +unordered_map indegree; +unordered_map> graph; + +//위상정렬 +vector topologicalSort(set &item) { //위상 정렬 + vector result; //결과값 벡터 선언 + priority_queue, greater<>> pq, pq_temp; + //우선 순위 큐 선언하기 + for (auto iter = item.begin(); iter != item.end(); iter++) { //아이템 값 iter로 반복 + if (indegree.find(*iter) == indegree.end()) //진입차수가 존재하지 않는다면(0 이라면) + pq.push(*iter); //해당 아이템값 큐에 저장 + } + while (!pq.empty()) { //큐가 빌때까지 반복 + string node = pq.top(); //해당 윗값 스트링에 저장 + pq.pop(); //값 빼기 + + result.push_back(node); //현재 정점 순서에 삽입 + for (int i = 0; i < graph[node].size(); i++) { //그래프 사이즈만큼 반복 + string next_node = graph[node][i]; //새로운 스트링 변수에 다음 노드값 저장 + indegree[next_node]--; //연결된 정점의 진입차수를 1씩 감소 + if (indegree[next_node] == 0) //연결된 정점의 진입차수가 0이 되었다면 + pq_temp.push(next_node); //우선 pq_temp에 삽입 + } + if (pq.empty() && !pq_temp.empty()) //이전에 진입차수가 0이였던 정점을 모두 탐색했고, 새로 탐색할 정점이 있다면 + swap(pq, pq_temp); //해당 정점과, 새로탐색정점 swqp 하기 + } + return result; //결과 벡터 반환 +} + +/** + * 문자열을 인덱스로 사용해야 하기때문에 map을 사용 + * + * !주의! 일반 map을 사용하면 시간초과. 따라서 map보다 검색 속도가 빠른 unordered_map 사용해야 함 + * -> map은 이진 탐색 트리 형태로 key값을 정렬해서 시간 복잡도가 logN 이지만, unordered_map은 hash형태로 저장해서 시간 복잡도가 1이다 + * !주의! 위상 정렬을 할 때 원래는 여러 결과가 가능하지만 해당 문제는 순서배치가 중요 (순서는 예제 3 참고) + * 1. 진입차수가 0인 정점들끼리 정렬해서 사용해야 함 + * 2. 진입차수가 0인 정점이 동시에 여러 개일 경우 해당 정점들을 먼저 순서에 배치해야 함. + * -> 따라서 우선순위 큐를 사용하여 진입차수가 0인 정점들을 관리. 이때 동시에 진입차수가 0이 된 정점들을 먼저 배치해야 하므로 우선순위 큐를 2개 사용. + */ + +int main() { + ios_base::sync_with_stdio(false); + cin.tie(NULL); + cout.tie(NULL); + //입출력 속도 향상하기 + set item; //존재하는 아이템(정점) + int n; //n값 선언 + string a, b; //스트링 a,b값 선언 + + //입력 + cin >> n; //n 값 입력 받기 + while (n--) { //n 번 만큼 입력 받기 + cin >> a >> b; //a < b + indegree[b]++; //진입차수 증가 + graph[a].push_back(b); //단방향 그래프 + item.insert(a); //아이템 삽입 + item.insert(b); //아이템 삽입 + } + + //연산 + vector result = topologicalSort(item); // item값 위상정렬후 result벡터에 저장 + + //출력 + if (result.size() != item.size()) { // 모든 아이템 출력 못할시 + cout << "-1\n"; // -1 출력 + return 0; + } + for (int i = 0; i < result.size(); i++) //결과 사이즈 만큼 반복 + cout << result[i] << '\n'; //결과 값 출력 + + return 0; +} \ No newline at end of file diff --git a/1207 - topological sort/2637.cpp b/1207 - topological sort/2637.cpp new file mode 100644 index 0000000..ecca673 --- /dev/null +++ b/1207 - topological sort/2637.cpp @@ -0,0 +1,69 @@ +// +// Created by 반예원 on 2021/12/21. +// + +#include +#include +#include + +using namespace std; +typedef pair ci; + +//위상정렬 + DP +vector> topologicalSort(int n, vector &indegree, vector> &graph) { + queue q; //큐 선언 + vector> dp(n + 1, vector(n + 1, 0)); //각 부품이 몇 개 필요한지 저장 + + for (int i = 1; i <= n; i++) { //n번만큼 반복 + if (!indegree[i]) { //진입차수가 0이라면 -> 기본 부품 + q.push(i); //큐값에 저장 + dp[i][i] = 1; //기본 부품 초기화 + } + } + while (!q.empty()) { //큐값이 비기 전까지 반복 + int node = q.front(); //큐맨위값 저장 + q.pop(); //빼기 + + for (int i = 0; i < graph[node].size(); i++) { //graph사이즈만큼 반복 + int next_node = graph[node][i].first; //node-i 그래프 값 새노드값에 저장 + int cnt = graph[node][i].second; //필요한 부품 수 + indegree[next_node]--; //연결된 정점의 진입차수를 1씩 감소 + if (!indegree[next_node]) //연결된 정점의 진입차수가 0이 되었다면 + q.push(next_node); //새 노드값 큐에 저장 + + for (int j = 1; j <= n; j++) //n번만큼 반복 + dp[next_node][j] += dp[node][j] * cnt; //(현재 정점을 이루는 부품 개수 * 필요한 현재 정점의 개수)를 더함 + } + } + return dp; //dp값 반환하기 +} + +/** + * 각 부품마다 종류별 필요한 부품 개수를 저장하기 위해 2차원 배열 사용 (행: 부품, 열: 행을 이루는데 각 부품별 필요한 개수) + * 위상 정렬 순서에 따라, 이어진 다음 정점의 부품별 개수를 (현재 정점을 이루는 부품별 개수 * 필요한 현재 정점 수)를 더해주며 구함 + */ + +int main() { + int n, m, x, y, k; + + //입력 + cin >> n >> m; //n,m 입력 받기 + vector> graph(n + 1, vector(0)); // graph 선언 + vector indegree(n + 1, 0); // indegree 벡터 선언 + while (m--) { //m번만큼 입력 받기 + cin >> x >> y >> k; //y -> x + indegree[x]++; //완제품 값 indegree벡터에 저장 + graph[y].push_back({x, k}); // 기본 부품 y값과 필요갯수 k값 입력 받기 + } + + //연산 + vector> result = topologicalSort(n, indegree, graph); + //위상 정렬 함수 사용하여 값 저장 + + //출력 + for (int i = 1; i <= n; i++) { + if (result[i][i]) //기본 부품이라면 + cout << i << ' ' << result[n][i] << '\n'; + } + return 0; +} \ No newline at end of file diff --git a/1207 - topological sort/3085.cpp b/1207 - topological sort/3085.cpp new file mode 100644 index 0000000..f33613e --- /dev/null +++ b/1207 - topological sort/3085.cpp @@ -0,0 +1,80 @@ +// +// Created by 반예원 on 2021/12/21. +// + +#include +#include +#include + +using namespace std; + +vector> board; //보드 선언 +int dr[2] = {1, 0}; +int dc[2] = {0, 1}; + +int cntCandy(int n, int row, int col, int dir) { //사탕 개수세기 + int ans = 0, cnt = 0; //ans와 cnt값 선언 + char cur = ' '; //cur 문자열 선언 + for (int i = 0; i < n; i++) { //해당 n만큼 반복하기 + if (cur == board[row][col]) { //연속된 사탕인경우 + cnt++; //갯수 증가 + ans = max(ans, cnt); //갯수와 ans비교후 최대값 갱신 + } else { //불연속 + cnt = 1; //1로 설정 + cur = board[row][col];// 해당 행,열의 보드 값 char에 대입 + } + row += dr[dir]; //row값 방향값 더하여 갱신 + col += dc[dir]; //col값 방향값 더하여 갱신 + } + return ans; //최종 ans 반환 +} + +int findCandy(int n) { + int ans = 0; //ans값 0으로초기화 + for (int i = 0; i < n; i++) { //n만큼 반복하기 + ans = max(ans, cntCandy(n, 0, i, 0)); //같은 열에 대해 + ans = max(ans, cntCandy(n, i, 0, 1)); //같은 행에 대해 + } + return ans; //최종 ans값 반환하기 +} + +int switchCandy(int n, int row, int col, char candy) { //사탕 바꾸기 + int ans = 0; //닶 초기화 + for (int i = 0; i < 2; i++) { //오른쪽, 아래에 있는 사탕과 바꿔보기 + int nr = row + dr[i], nc = col + dc[i]; // nr, nc에 좌표 값 늘려서 값 저장 + if (nr < n && nc < n && candy != board[nr][nc]) { //사탕 바꿀 수 있는 경우 + swap(board[row][col], board[nr][nc]); // 바꾸기 + ans = max(ans, findCandy(n)); // + swap(board[row][col], board[nr][nc]); + } + } + return ans; +} + +/** + * 입력 범위가 크지 않으므로 바꿀 수 있는 사탕을 모두 바꿔보며 먹을 수 있는 사탕 계산 + * 오른쪽, 아래에 있는 사탕과만 바꿔도 모든 경우 고려 가능(왼쪽, 위 고려 X) + * + * 1. 사탕의 색이 다른 사탕만 교환하기 + * 2. 각 열, 행이 모두 같은 사탕일 때 사탕의 개수가 갱신되지 않도록 주의 (ans 갱신을 line 18~21에서 하는 경우) + */ +int main() { + int n, max_candy = 0; + + //입력 + cin >> n; //n값 입력 받기 + board.assign(n, vector(n, ' ')); // 보드값 초기화하기 + for (int i = 0; i < n; i++) //입력받은 n만큼 반복하여 + for (int j = 0; j < n; j++) + cin >> board[i][j]; //보드값 넣기 + + //연산 + for (int i = 0; i < n; i++) { //이중반복하여 연산 진행 + for (int j = 0; j < n; j++) + max_candy = max(max_candy, switchCandy(n, i, j, board[i][j])); // switchCandy 함수 리턴 후 값과 최대 캔디 비교후 + //최대값 갱신 + } + + //출력 + cout << max_candy; // 최대값 출력 +} \ No newline at end of file diff --git a/1207 - topological sort/buildRaceRoad.cpp b/1207 - topological sort/buildRaceRoad.cpp new file mode 100644 index 0000000..f22e0c3 --- /dev/null +++ b/1207 - topological sort/buildRaceRoad.cpp @@ -0,0 +1,74 @@ +// +// Created by 반예원 on 2021/12/21. +// + +#include +#include +#include + +using namespace std; +const int INF = 1e9; + +int dr[4] = {-1, 1, 0, 0}; +int dc[4] = {0, 0, -1, 1}; + +struct status { //행, 열, 방향, 비용 + int r, c, dir, cost; +}; + +int bfs(int n, vector> board) { //bfs 함수 + vector>> dist(n, vector>(n, vector(4, INF))); + //경로 벡터 선언 + queue q; // 큐 선언 + + //시작점 초기화 (시작점은 모든 방향일 수 있음) + for (int i = 0; i < 4; i++) { //4번 반복하여 + dist[0][0][i] = 0; //벡터의 i값 0으로 저장 + q.push({0, 0, i, 0}); //큐에 r,x,dir,cost저장 + } + + while (!q.empty()) { //큐가 빌때 까지 반복 + int row = q.front().r; //해당 큐의 행 값 선언 + int col = q.front().c;//해당 큐의 열 값 선언 + int dir = q.front().dir;//해당 큐의 방향 값 선언 + int cost = q.front().cost;//해당 큐의 비용 값 선언 + q.pop(); //큐 맨윗값 제거 + + for (int i = 0; i < 4; i++) { //4번 반복하여 + int nr = row + dr[i]; //좌표 값 선언 + int nc = col + dc[i];//좌표 값 선언 + if (nr < 0 || nr >= n || nc < 0 || nc >= n || board[nr][nc]) //건설이 가능한 경로라면 + continue; //반복 + int nw = i == dir ? cost + 100 : cost + 600; //방향에 따른 비용 + if (nw < dist[nr][nc][i]) { //더 적은 비용으로 경주로를 건설할 수 있다면 + dist[nr][nc][i] = nw; //해당 dist벡터 값에 비용 대입 + q.push({nr, nc, i, nw}); //큐에 새롭게 저장 + } + } + } + int min_cost = INF; //최소 비용 초기화 + for (int i = 0; i < 4; i++) //4개의 방향 중 가장 작은 값 + min_cost = min(min_cost, dist[n - 1][n - 1][i]); //최소비용과, dist의 각 방향 값 최소값 찾기 + return min_cost; //최소값 반환하기 +} + +int solution(vector> board) { + return bfs(board.size(), board); //bfs함수 반환 +} + +/** + * 하나의 좌표에 대해 자동차는 4개의 방향을 통해 진입할 수 있음. + * (행, 열) 정보만 저장했던 기존의 방식과 달리 (행, 열, 방향) 정보까지 저장! -> 3차원 배열 + * 배열에 저장되는 값은 해당 좌표와 방향으로 건설할 수 있는 경주로 중 가장 비용이 저렴한 것 + * !주의! 시작점은 방향이 따로 없으므로 초기화시 모든 방향에 대해 정보를 입력함 + * + * 방향에 따른 비용에 차이를 두며 구현 + * 공식 풀이 : https://tech.kakao.com/2020/07/01/2020-internship-test/ + */ +int main() { + //보드값 선언 + vector> board = {{0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}}; + cout << solution(board); //정답 출력 +} \ No newline at end of file diff --git a/1207 - topological sort/kakaocardMatch.cpp b/1207 - topological sort/kakaocardMatch.cpp new file mode 100644 index 0000000..d90650f --- /dev/null +++ b/1207 - topological sort/kakaocardMatch.cpp @@ -0,0 +1,142 @@ +// +// Created by 반예원 on 2021/12/21. +// + +#include +#include +#include +#include + +using namespace std; +typedef pair ci; +const int SIZE = 4; + +int dr[4] = {-1, 1, 0, 0}; +int dc[4] = {0, 0, -1, 1}; + +vector> findCard(vector> &board) { //존재하는 모든 카드의 위치를 리턴하는 함수 + vector> card_pos(7, vector(0)); //최대 카드 수 + int cnt = 0; //cnt 값 초기화 + for (int i = 0; i < SIZE; i++) { // 배열크기만큼 반복 + for (int j = 0; j < SIZE; j++) { // 반복 + cnt = max(cnt, board[i][j]); //cnt값에, cnt와 board배열 최대값 비교후 갱신 + if (board[i][j]) //카드라면 위치 저장 + card_pos[board[i][j]].emplace_back(i, j); //카드 값 있다면 위치 저장하기 + } + } + card_pos.resize(cnt + 1); //실제 존재하는 카드의 수만큼 크기 조절 + return card_pos; //저장한 카드 위치 벡터 반환 +} + +pair ctrl(int row, int col, int dir, vector> &tmp) { //컨트롤로 이동하는 좌표를 리턴하는 함수 + while (true) { //무한 반복 + row += dr[dir]; //해당 row에 방향값 더하기 + col += dc[dir];//해당 col에 방향값 더하기 + if (row < 0 || row >= SIZE || col < 0 || col >= SIZE) //해당 방향에 카드가 하나도 없다면 그 방향의 가장 마지막 칸으로 이동 + return make_pair(row - dr[dir], col - dc[dir]); + if (tmp[row][col] != 0) //누른 키 방향에 있는 가장 가까운 카드 + return make_pair(row, col); //좌표 반환 + } +} + +int bfs(int r1, int c1, int r2, int c2, vector> &tmp) { //현재 커서에서 목표 카드로 이동하는 가장 적은 비용을 리턴하는 함수 + vector> visited(SIZE, vector(SIZE, 0)); // 방문했는지 확인 배열 + queue> q; // 큐 선언 + visited[r1][c1] = 1; //반드시 엔터를 누르게 될 것. 엔터를 미리 눌렀다 가정하고 1로 표시 + q.push({r1, c1}); //큐에 값 넣기 + + while (!q.empty()) { //빈값이 없을때까지 반복 + int row = q.front().first; // 큐 프론트값 첫번째꺼 row에 대입 + int col = q.front().second; // 큐 프론트값 두번째꺼 col 에 대입 + int dist = visited[row][col]; // 방문한 위치 값 체크 + q.pop(); // 위에값 제거 + + if (row == r2 && col == c2) //목표 카드에 도달했다면 + return dist; // 방문한 위치 값 반환 + + for (int i = 0; i < 4; i++) { //컨트롤 입력 이동 + pair np = ctrl(row, col, i, tmp); //컨트롤 이동 함수 pair저장 + if (visited[np.first][np.second]) // 해당 좌표 값의 first, second 값 존재 여부 확인 + continue; //반복 + visited[np.first][np.second] = dist + 1; // visited에 체크하기 + q.push(np); //큐에 값 대입 + } + + for (int i = 0; i < 4; i++) { //방향키 입력 이동 + int nr = row + dr[i], nc = col + dc[i]; + if (nr < 0 || nr >= 4 || nc < 0 || nc >= 4 || visited[nr][nc]) + continue; + visited[nr][nc] = dist + 1; + q.push({nr, nc}); + } + } + return -1; //목표 카드에 도달하지 못함 (실제로는 일어나지 않는 일) +} + +int matchCard(int bit, int num, int r, int c, vector &seq, vector> &cards, vector> tmp){ //조합에 대해 카드를 매칭하는 함수 + int ans = 0; // 정답 0으로 초기화 + for (int i = 0; i < num; i++) { // 카드개수 만큼 반복 + int cur = seq[i]; //이번에 매칭할 캐릭터 + int now = 0; //해당 캐릭터의 0번째 카드부터 선택한다고 가정 + if (bit & (1 << i)) //만약 해당 위치의 비트가 1을 표시했다면 1번째 카드부터 선택 + now = 1; // now 변경 + + for (int j = 0; j < 2; j++) { //2번 반복하기 + now = (now + j) % 2; //이번에 매칭할 카드 인덱스 + int nr = cards[cur][now].first, nc = cards[cur][now].second; //이번에 매칭할 카드 위치 + ans += bfs(r, c, nr, nc, tmp); //현재 커서에서 목표 카드까지 이동하는 비용 + + //카드 삭제 + 커서 이동 + tmp[nr][nc] = 0; //카드 삭제하기 + r = nr; // nr값 r에 대입하여 커서 이동 + c = nc; //nc값 c에 대입하여 커서 이동 + } + } + return ans; +} + +int solution(vector> board, int r, int c) { + int answer = 1e9; //답 초기화 + + vector> cards = findCard(board); //존재하는 모든 카드의 위치 + int card_cnt = cards.size() - 1; //카드의 개수 + + vector seq(card_cnt); //순열을 위한 배열 + for (int i = 0; i < card_cnt; i++) // 카드의 개수 만큼 반복 + seq[i] = i + 1; // //배열에 저장 + + do { //가능한 모든 카드 순서에 대해 + for (int bit = 0; bit < (1 << card_cnt); bit++) { //0, 1번째 카드 중 어떤 카드를 먼저 뒤집을지 결정 + answer = min(answer, matchCard(bit, card_cnt, r, c, seq, cards, board)); //더 작은값으로 갱신 + } + } while (next_permutation(seq.begin(), seq.end())); + return answer; +} + +/** + * 아이디어 + * 1. 범위가 크지 않음 + * 2. 존재하는 모든 카드의 종류는 6개, 카드 2장이 한 쌍을 이룬다. + * 3. 뒤집을 카드 순서를 정하는 모든 경우의 수는 6!(카드 순서) * 2^6(2개의 카드 중 어떤 카드를 먼저 뒤집을지) -> 브루트포스 가능 + * 4. 이번에 목표로 하는 카드에 대해 현재 커서에서 목표 카드까지 가는 최단 경로를 구하며 이동 횟수 전부 구하고 최솟값 갱신 + * + * 구현 + * 1. 존재하는 모든 카드의 위치를 저장하며 카드의 개수 세기 (findCard) + * 2. 가능한 모든 카드의 순서(next_permutation)와 각 카드를 뒤집을 순서(bitmask)를 결정 + * ex) seq = {3, 1, 2}, bit = 011 일 때 + * 3번, 1번, 2번 카드의 순서로 뒤집는다. + * 3번 카드는 1번째 카드부터, 1번 카드는 0번째 카드부터, 2번 카드는 1번째 카드부터 뒤집는다. + * bit의 011이 앞에서부터 각각 1, 2, 3번 카드와 대응함 + * 3. 현재 카드 순서와 각 카드를 뒤집는 순서에 대한 커서 이동 횟수 계산 (matchCard) + * 현재 커서 위치와 목표 카드 위치에 대해 bfs 함수 실행 + * 컨트롤 입력을 고려해야 하기 때문에 4방향에 대한 방향 이동에 추가로 컨트롤 입력도 처리한다.(ctrl) + * 4. 매 조합에 따라 board가 갱신되므로 board를 복사한 tmp 사용 + * 공식 풀이 : https://tech.kakao.com/2021/01/25/2021-kakao-recruitment-round-1/ + */ +int main() { + vector> board = {{1, 0, 0, 3}, + {2, 0, 0, 0}, + {0, 0, 0, 2}, + {3, 0, 1, 0}}; + cout << solution(board, 1, 0); +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ca2b0a7..28b256a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,4 +8,4 @@ add_executable(CMAKE_ROOT #"0903- sort/13458.cpp" # "0903- sort/13458.cpp" #"0903- sort/11399.cpp" - "1116 - tree/14503.cpp" "1116 - tree/2011.cpp" "1116 - tree/14675.cpp" "1116 - tree/15681.cpp" "1116 - tree/1967.cpp" "1123 - union/16236.cpp") \ No newline at end of file + "1207 - topological sort/2252.cpp" "1207 - topological sort/kakaocardMatch.cpp" "1207 - topological sort/3085.cpp" "1207 - topological sort/buildRaceRoad.cpp" "1207 - topological sort/23059.cpp" "1207 - topological sort/2637.cpp") \ No newline at end of file