Skip to content

Commit

Permalink
[유니온 파인드] 11월 23일 -update
Browse files Browse the repository at this point in the history
필수 16236,
선택 10775, 1976, 20040 추가제출 합니다.
  • Loading branch information
spiritstone committed Nov 30, 2021
1 parent 372d3c8 commit 4572635
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 0 deletions.
61 changes: 61 additions & 0 deletions 11월 23일 - 유니온 파인드/10775_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Created by user on 2021-11-30.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> parent;

//Find 연산
int findParent(int node) {
if (parent[node] == -1) //루트 정점
return node; // 입력한 노드가 루트 정점이므로 노드 반환
return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기
}

//Union 연산
bool unionGate(int gate) {
int possible_gate = findParent(gate); //도킹 가능한 게이트
if (possible_gate == 0) //도킹 가능한 게이트가 없으므로
return false; // 도킹 불가
parent[possible_gate] = possible_gate - 1; //도킹 처리
return true; // 도킹 가능
}

/**
* 그리디
* 1. 1 ~ gi 까지의 게이트 중 어떤 게이트에 도킹해야 할까?
* 2. 1번 게이트부터 도킹한다면... g1 = 3, g2 = 1인 경우 1대의 비행기만 도킹할 수 있음
* 3. gi번 게이트부터 도킹한다면... g1 = 3, g2 = 1인 경우 2대의 비행기 모두 도킹할 수 있음
* -> gi번 게이트부터 1번 게이트까지 가능한 게이트를 찾으면 바로 도킹 (그리디)
*
* 유니온 파인드
* 1. 만약 1번 게이트가 비어있고, 2 ~ 1,000번 게이트가 도킹되어 있으며 gi = 1,000인 입력이 들어왔을 때...
* 2. 1,000번 게이트부터 하나하나 거슬러 올라가며 1번 게이트까지 가는 것은 비효율적 (시간초과)
* 3. 도킹할 때마다 이번에 도킹한 게이트의 바로 이전 게이트를 집합의 루트 정점으로 만들어 바로 접근할 수 있도록 설정
*
* 비슷한 문제 : https://tech.kakao.com/2020/04/01/2019-internship-test/ (문제 4)
* 문제 해설 도움 받은 링크: https://steady-coding.tistory.com/114
*/
int main() {
int g, p, gi; // g: 게이트의 수, p: 비행기의 수, gi: 맨처음 도킹 시도할 게이트 번호

//입력
cin >> g >> p;
parent.assign(g + 1, -1); // 게이트의 수만큼 정점 노드 초기화
for (int i = 0; i < p; i++) { // p개의 줄에
cin >> gi; // gi가 주어진다.

//연산
if (!unionGate(gi)) { //도킹 불가능
cout << i; // 몇 개까지 도킹했는지 출력
return 0; // 프로그램 종료
}
}

//출력
cout << p; // 비행기 모두 모두 도킹 가능하다면(종료되지 않고 반복문 탈출했다면) p 출력
}
114 changes: 114 additions & 0 deletions 11월 23일 - 유니온 파인드/16236_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// Created by user on 2021-11-30.
//

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;
const int INF = 401; // 공간의 최대
typedef pair<int, int> ci; // 상어의 위치

pair<int, ci> nextPos(int n, int shark_size, ci &shark, vector<vector<int>> &board) {
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
// 순서대로 상, 하, 좌, 우

int min_dist = INF; // 최소 값의 초기값은 최대로 설정
queue<ci> q; //상어가 갈 수 있는 곳
vector<vector<int>> visited(n, vector<int>(n, 0)); //상어의 방문 여부
vector<ci> list; //상어가 먹을 수 있는 물고기들의 위치

visited[shark.first][shark.second] = 1;
q.push(shark); // 방문했다고 표시하고 상어가 갈 수 있는 곳으로 추가
while (!q.empty()) { // 상어가 갈 수 있는 곳이 존재하는 동안
int row = q.front().first; // 행 설정
int col = q.front().second; //
int dist = visited[row][col]; // 거리
q.pop(); // 큐 상단에 있는 값 pop

if (dist >= min_dist) //최단거리 이상은 탐색할 필요 없음
continue;
for (int i = 0; i < 4; i++) {
int nr = row + dr[i]; // 위치 바꿔가면서
int nc = col + dc[i];
if (nr < 0 || nr >= n || nc < 0 || nc >= n || visited[nr][nc] || board[nr][nc] > shark_size)
// 이동 불가하다면(보드의 범위 밖이거나 아무것도 없다면)
continue; // 탐색할 필요 없다

visited[nr][nc] = visited[row][col] + 1; // 범위 내라면 상어의 위치 + 1
if (board[nr][nc] && board[nr][nc] < shark_size) { //먹을 수 있는 물고기 발견
list.emplace_back(nr, nc); // 먹을 수 있는 물고기들의 위치에 추가
min_dist = visited[nr][nc]; // 최소 거리 갱신
continue;
}
q.push({nr, nc}); // 먹을 수 있는 물고기를 찾았다면 더 나갈 필요 없다
}
}

if (list.empty()) //상어가 갈 수 있는 곳이 없음
return {min_dist, {-1, -1}}; //

sort(list.begin(), list.end(), [](const ci &p1, const ci &p2) { //정렬
if (p1.first != p2.first) // 조건에 맞는 물고기의 좌표가 맨 앞으로 올 수 있게 정렬
return p1.first < p2.first; // 위로 먼저 이동
return p1.second < p2.second; // 왼쪽으로 먼저 이동
});
return {min_dist - 1, list[0]}; // 조건에 맞는 물고기의 좌표 넘겨줌
}

int simulation(int n, pair<int, int> &shark, vector<vector<int>> &board) {
int ans = 0, size = 2, cnt = 0; // size: 상어의 크기 (초기값 2로 설정), cnt: 먹은 물고기의 수
while (true) {
pair<int, ci> result = nextPos(n, size, shark, board); // 위치 get
if (result.first == INF) //더 이상 먹을 수 있는 물고기가 공간에 없음
break; // 반복문 종료
ans += result.first; // 먹을 수 있다면 정답에 추가
cnt++; // 먹은 물고기 수 증가
if (cnt == size) { //상어 크기 증가
cnt = 0; // 계산했으니 cnt 0으로 초기화
size++; // 크기 증가
}

//상어 이동
ci pos = result.second;
board[shark.first][shark.second] = 0; // 그 위치에 있는 물고기를 먹었읜 그 위치는 이제 빈 칸
shark = pos; // 상어 위치 재설정
}
return ans;
}

/**
* 1. 상어로부터 가장 가까운 거리에 있는 모든 물고기 탐색 (BFS)
* 2. 우선순위 조건에 맞추어 먹으러 갈 물고기 확정
* 탐색하는 방향에 우선순위를 두는 걸로 해결되지 않음! (예제 입력 4) 정렬 필요
* 3. 상어가 이동할 수 있는 곳이 없을 때까지 BFS 탐색 반복
*
* 입력 범위가 작기 때문에 매번 BFS 탐색을 반복해도 시간 초과 X
* 가능한 물고기의 최대 마리 수 : 399마리
* 최대 BFS 탐색 횟수 : 399회, 1회 탐색마다 while 문은 최대 400회 미만으로 순회
* 총 연산 횟수 약 160000번으로 충분히 가능
*
* 해설 : https://myunji.tistory.com/378
* *글 자체는 별로 도움이 안되고...그냥 리팩토링하면 코드가 이렇게 되는구나 정도만 봐주세요
*/
int main() {
int n; // 공간의 크기: n X n
pair<int, int> shark_pos; // 상어의 위치

//입력
cin >> n;
vector<vector<int>> board(n, vector<int>(n)); // n X n만큼의 공간 생성
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> board[i][j]; // 각 칸의 상태 입력받기
if (board[i][j] == 9) //상어의 초기 위치
shark_pos = make_pair(i, j); // 초기 위치를 벡터값으로 저장
}
}

//연산 & 출력
cout << simulation(n, shark_pos, board);
}
61 changes: 61 additions & 0 deletions 11월 23일 - 유니온 파인드/1976_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Created by user on 2021-11-30.
//
#include <iostream>
#include <vector>

using namespace std;

vector<int> parent;

//Find 연산
int findParent(int node) {
if (parent[node] < 0) //루트 정점
return node; // 입력한 노드가 루트 정점이므로 노드 반환
return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기
}

//Union 연산
void unionInput(int x, int y) {
int xp = findParent(x); // x의 루트 정점
int yp = findParent(y); // y의 루트 정점

if (xp == yp) //같은 집합에 있다면 유니온 할 수 없음
return; // 할 수 없으므로 함수 종료
if (parent[xp] < parent[yp]) { //새로운 루트 xp
parent[xp] += parent[yp]; // xp에 yp 트리 추가
parent[yp] = xp; // yp의 루트 정점을 xp로 설정
} else { //새로운 루트 yp
parent[yp] += parent[xp]; // yp에 xp 추가
parent[xp] = yp; // xp의 루트 정점을 yp로 설정
}
}

/**
* 입력으로 주어지는 i, j 도시의 연결정보를 통해 서로소 집합을 만든 후,
* 여행 계획으로 세운 도시들이 모두 같은 집합에 속하는지 확인하는 문제
*/

int main() {
int n, m, a, b, input; // n: 도시의 수, m: 여행 계획에 속한 도시들의 수

//입력
cin >> n >> m;
parent.assign(n + 1, -1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) { // i번째 줄의 j번째 수는 i번 도시와 j번 도시의 연결 정보
cin >> input; // 1이면 연결, 0이면 연결 안되어있음
if (input) //두 정점이 연결되어 있다면
unionInput(i, j); // Union 연산
}
}
cin >> a; //시작 정점
while (--m) { // 계획에 속한 도시들 수 줄여가면서
cin >> b; // 여행 계획 순서
if (findParent(a) != findParent(b)) { //서로 다른 집합이라면 -> 해당 경로 불가능
cout << "NO"; // No 출력
return 0; // 함수 종료
}
}
cout << "YES"; // 가능하므로 YES 출력
}
55 changes: 55 additions & 0 deletions 11월 23일 - 유니온 파인드/20040_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Created by user on 2021-11-30.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> parent;

//Find 연산
int findParent(int node) {
if (parent[node] < 0) //루트 정점
return node; // 입력한 노드가 루트 정점이므로 노드 반환
return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기
}

//Union 연산
bool unionInput(int x, int y) {
int xp = findParent(x); // x의 루트 정점
int yp = findParent(y); // y의 루트 정점

if (xp == yp) //같은 집합에 있다면 유니온 할 수 없음
return false; // 할 수 없으므로 false 반환
if (parent[xp] < parent[yp]) { //새로운 루트 xp
parent[xp] += parent[yp]; // xp에 yp 트리 추가
parent[yp] = xp; // yp의 루트 정점을 xp로 설정
} else { //새로운 루트 yp
parent[yp] += parent[xp]; // yp에 xp 추가
parent[xp] = yp; // xp의 루트 정점을 yp로 설정
}
return true; // Union 가능하므로 true 반환
}
/**
* 사이클이 발생한 순간 = 같은 집합에 있는 원소 두 개를 유니온하려 할 때
* unionInput 함수의 반환형을 bool로 선언하여 cycle이 생성되는 순간 발견하기
*/
int main() {
int n, m, x, y; // n: 점의 개수, m: 게임 차례, x, y: 플레이어가 자신의 차례에 선택한 두 개의 점

//입력
cin >> n >> m;
parent.assign(n, -1); // 점의 개수만큼 -1로 초기화
for (int i = 0; i < m; i++) { // 차례만큼
cin >> x >> y; // 선택한 두 개의 점 입력

//연산 & 출력
if (!unionInput(x, y)) { //유니온 할 수 없음 = 사이클이 생성됨
cout << i + 1; // i의 시작 값을 0으로 설정했으므로 사이클이 만들어진 차례의 번호는 i+1
return 0; // 종료
}
}
cout << 0; // m번의 차례를 모두 처리한 이후에도 종료되지 않았으므로 0 출력
}

0 comments on commit 4572635

Please sign in to comment.