Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[유니온 파인드] 11월 30일 #17

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions 11월 23일 - 유니온 파인드/1043.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// Created by user on 2021-11-30.
//

#include <iostream>
#include <vector>

using namespace std;

vector<bool> truth; //진실을 아는지 여부
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 (truth[xp] || truth[yp]) //둘 중 하나라도 진실을 믿는 사람이 있다면 표시
truth[xp] = truth[yp] = true; //둘다 진실을 믿는다고 표시
if (parent[xp] < parent[yp]) { //새로운 루트 xp
parent[xp] += parent[yp];
parent[yp] = xp;
} else { //새로운 루트 yp
parent[yp] += parent[xp];
parent[xp] = yp;
}
}

int liarParty(vector<int> &parties) {
int cnt = 0; //과장할 수 있는 파티 개수
for (int i = 0; i < parties.size(); i++) {
int root = findParent(parties[i]); //파티 집합의 루트 정점
if (!truth[root])
cnt++; //진실을 믿는 사람이 없으면 개수 늘려줌
}
return cnt; //리턴
}

/**
* 1. 각 사람들은 다양한 파티를 통해 연결됐다고 할 수 있음
* 2. 연결된 사람들은 같은 집합에 속함
* 3. 각 집합에 속한 사람들 중 한 명이라도 진실을 안다면 그 집합의 사람들이 속한 파티에는 거짓말을 할 수 없음
* -> 유니온 파인드로 사람들을 집합으로 묶은 뒤, 파티마다 거짓말을 할 수 있는지 확인하기
*
* !주의! 파티 정보를 입력받으며 바로 거짓말 가능 여부를 판단할 수 없음 (예제 입력 4)
* 각 파티에서 한 사람만 저장해둔 뒤, 마지막에 거짓말 가능 여부 한 번에 판단
*/
int main() {
int n, m;

//입력
cin >> n >> m; //사람 수 n, 파티 수 m
truth.assign(n + 1, false); //진실을 아는 사람 수 벡터 할당
parent.assign(n + 1, -1); //파티 참여 사람 수 벡터 할당

int init, p;
cin >> init; //진실을 아는 사람 수
while (init--) { //진실을 아는 사람들
cin >> p; //번호 넣어줌
truth[p] = true; //진실을 앎을 표시
}

int cnt, first_person, people;
vector<int> parties;
while (m--) {
cin >> cnt >> first_person;
//파티에 오는 사람의 수와 첫번째 사람의 번호

//연산
parties.push_back(first_person); //파티 정보로 각 파티의 첫번째 사람만 저장
while (--cnt) {
cin >> people; //파티에 오는 사람들 번호
unionInput(first_person, people); //유니온
}
}

int ans = liarParty(parties); //과장 파티 개수 리턴해주는 함수 호출

//출력
cout << ans;
}
114 changes: 114 additions & 0 deletions 11월 23일 - 유니온 파인드/16236.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(); //큐에서 뺌

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; //거리 갱신
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; //답, 사이즈, 물고기 개수
while (true) {
pair<int, ci> result = nextPos(n, size, shark, board);
//이동 값
if (result.first == INF) //더 이상 먹을 수 있는 물고기가 공간에 없음
break; //탈출
ans += result.first; //이동 시간 더해줌
cnt++; //물고기 개수 늘림
if (cnt == size) { //상어 크기 증가
cnt = 0; //다시 초기화
size++; //상어크기도 늘려줌
}

//상어 이동
ci pos = result.second; //이동값에 따라 이동 시켜줌
board[shark.first][shark.second] = 0; //이동한 곳은 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;
pair<int, int> shark_pos;

//입력
cin >> n; //공간의 크기
vector<vector<int>> board(n, vector<int>(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);
}
67 changes: 67 additions & 0 deletions 11월 23일 - 유니온 파인드/16562.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// 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; //리턴
//앞선 라이브코딩 코드와 달리 parent[xp]가 클때
if (parent[xp] > parent[yp]) //새로운 루트 xp
//앞선 라이브코딩 코드와 달리 parent[xp] += parent[yp] 해주는 과정 없음
parent[yp] = xp;
else //새로운 루트 yp
parent[xp] = yp;
}

int friendshipCost(int n) {
int sum = 0; //합 초기화
for (int i = 1; i <= n; i++) {
if (parent[i] < 0) //루트 정점이라면
sum += -parent[i]; //음수인 루트값(친구비)를 양수로 바꿔서 더해줌
}
return sum; //합 리턴
}

int main() {
int n, m, k, v, w, cost;

//입력
cin >> n >> m >> k; //학생수, 친구관계 수, 가지고 있는 돈
parent.assign(n + 1, 0); //부모 벡터 할당
for (int i = 1; i <= n; i++) {
cin >> cost; //친구비
parent[i] = -cost; //루트 정점에 필요한 친구비(음수)를 저장
}

//연산
while (m--) {
cin >> v >> w; //학생v와 학생w가 서로 친구
unionInput(v, w); //유니온
}

int ans = friendshipCost(n); //친구비 계산

//출력
if (ans <= k) //k보다 작으면(친구를 다 사귈 수 있으면)
cout << ans; //답 출력
else
cout << "Oh no"; //아니면 oh no
}
59 changes: 59 additions & 0 deletions 11월 23일 - 유니온 파인드/1717.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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];
parent[yp] = xp;
} else { //새로운 루트 yp
parent[yp] += parent[xp];
parent[xp] = yp;
}
}

int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);

int n, m, cmd, a, b;

//입력
cin >> n >> m; //n+1개의 집합, m개의 연산개수
parent.assign(n + 1, -1); //집합 벡터 할당
while (m--) {
cin >> cmd >> a >> b; //a가 포함되어 있는 집합과 b가 포함되어있는 집합을
//합치거나(0) 확인함(1)

//연산
if (cmd == 0) { //유니온
unionInput(a, b);
}
if (cmd == 1) { //파인드
if (findParent(a) == findParent(b))
cout << "YES\n"; //같은 집합이면
else
cout << "NO\n"; //다른 집합이면
}
}
}
63 changes: 63 additions & 0 deletions 11월 23일 - 유니온 파인드/18111.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Created by user on 2021-11-30.
//

#include <iostream>
#include <vector>

using namespace std;
const int INF = 1e9;

int mineLand(int n, int m, int b, int height, vector<vector<int>> &land) {
int tot_time = 0; //토탈 시간
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (land[i][j] > height) { //블록 제거
b += (land[i][j] - height); //제거하는 블록 개수
tot_time += 2 * (land[i][j] - height); //걸리는 시간
} else if (land[i][j] < height) { //블록 쌓기
b -= (height - land[i][j]); //쌓아주는 블록 개수
tot_time += (height - land[i][j]); //걸리는 시간
}
}
}
if (b < 0) //최종적으로 블럭이 음수면 불가능한 높이
return INF + 1; //불가능한 값 리턴
return tot_time; //양수면 값 리턴
}

/**
* 1. 가장 낮은 땅의 높이를 h라고 할 때, h-1의 높이를 만드는건 h보다 2*(N*M)의 시간이 더 소요됨
* 2. 가장 높은 땅의 높이를 h라고 할 때, h+1의 높이를 만드는건 h보다 (N*M)의 시간이 더 소요됨
* -> 따라서 땅의 높이가 될 수 있는 후보는 (가장 낮은 땅) ~ (가장 높은 땅)
* -> 가능한 모든 높이에 대해 브루트포스 연산해도 시간 초과 X
*
* !주의! 당장 쌓을 블록이 없더라도 언젠가 다른 곳의 블록을 제거해서 쌓을 수 있음.
*/
int main() {
int n, m, b, min_height = 256, max_height = 0;

//입력
cin >> n >> m >> b; //세로, 가로, 인벤토리내 블록 개수
vector<vector<int>> land(n, vector<int>(m, 0)); //land 벡터 할당
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> land[i][j]; //값 할당
min_height = min(min_height, land[i][j]); //제일 낮은 땅 높이
max_height = max(max_height, land[i][j]); //제일 높은 땅 높이
}
}

//연산
int min_time = INF, height = 0;
for (int i = min_height; i <= max_height; i++) {
int t = mineLand(n, m, b, i, land); //시간 구하기
if (t <= min_time) { //가장 빨리 작업이 끝나는 높이
min_time = t; //시간 갱신
height = i; //높이 갱신
}
}

//출력
cout << min_time << ' ' << height;
}
Loading