Skip to content

Commit

Permalink
[최소 신장 트리] 11월 30일 -update
Browse files Browse the repository at this point in the history
필수 2문제, 선택 3문제 추가제출합니다.
  • Loading branch information
spiritstone committed Dec 7, 2021
1 parent f0cb886 commit 0482247
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 0 deletions.
72 changes: 72 additions & 0 deletions 11월 30일 - 최소 신장 트리/1368_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// Created by user on 2021-12-07.
//

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

using namespace std;
const int INF = 1e5 + 1;

int prim(int size, int start, vector<vector<int>> &graph) {
int sum = 0;
vector<int> dist(size, INF); //각 논까지의 비용
vector<bool> visited(size, false); //논 방문 여부
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq; // 우선수위 큐 선언

//초기화
dist[start] = 0; // 거리 초기화
pq.push({0, start}); // 정점 초기화

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}); // 큐에 해당 정점 추가
}
}
}
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<vector<int>> graph(n + 1, vector<int>(n + 1, 0)); // 논 크기의 그래프 생성
for (int i = 0; i < n; i++) { //수원으로부터 물을 끌어오는 비용
cin >> w; // 비용 입력
graph[i][n] = graph[n][i] = w; // 각 거리에 대해 비용 삽입
}

for (int i = 0; i < n; i++) { // 논 크기만큼 반복
for (int j = 0; j < n; j++) // 논 크기만큼 반복
cin >> graph[i][j]; //논들 사이에서 물을 끌어오는 비용
}

cout << prim(n + 1, n, graph); //수원에서 시작하는 프림 알고리즘
}
125 changes: 125 additions & 0 deletions 11월 30일 - 최소 신장 트리/16235_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// Created by user on 2021-12-07.
//

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

using namespace std;
typedef vector<vector<int>> matrix;
typedef tuple<int, int, int> tp;

// 봄 - 나무가 자신의 나이만큼 양분을 먹는다.
queue<tp> spring(matrix &land, deque<tp> &tree, queue<pair<int, int>> &breeding_tree) {
queue<tp> dead_tree; // 죽은 나무
int size = tree.size(); // 나무 수
while (size--) { //모든 나무 검사
int age = get<0>(tree.front()); //나이
int row = get<1>(tree.front()); //
int col = get<2>(tree.front()); //
tree.pop_front(); // 정보를 다 저장한 나무는 큐에서 제거

if (land[row][col] < age) { //자신의 나이만큼 양분을 먹을 수 없다면
dead_tree.push({age, row, col}); // 죽은 나무 큐에 정보와 함께 추가
continue; // 코드 계속
}
land[row][col] -= age; // 나무가 먹은 만큼 땅에서 양분 제거
tree.emplace_back(age + 1, row, col); // 양분 먹고 나이 증가시켜서 큐에 추가
if ((age + 1) % 5 == 0) //나이가 5의 배수라면
breeding_tree.push({row, col}); // 번식하는 나무 큐에 추가
}
return dead_tree; // 여름에 죽은 나무를 양분으로 변화시키기 위해 죽은 나무 큐 반환
}

// 여름 - 죽은 나무가 양분으로 변화한다.
void summer(queue<tp> &dead_tree, matrix &land) {
while (!dead_tree.empty()) { // 죽은 나무 큐에 있는 모든 나무에 대해서
int age = get<0>(dead_tree.front()); //죽은 나무의 나이
int row = get<1>(dead_tree.front()); //죽은 나무의 행 위치
int col = get<2>(dead_tree.front()); //죽은 나무의 열 위치
dead_tree.pop(); // 정보 저장된 죽은 나무는 큐에서 제거
land[row][col] += (age / 2);
// 죽은 나무마다 나이를 2로 나눈 값이 나무가 있던 자리에 양분으로 추가되고,
// 소수점 아래는 버리기 때문에 몫 연산만
}
}

void fall(int n, deque<tp> &tree, queue<pair<int, int>> &breeding_tree) {
int dr[8] = {-1, 1, 0, 0, -1, -1, 1, 1}; // 인접한 8개의 칸에 대한 row 좌표
int dc[8] = {0, 0, -1, 1, -1, 1, -1, 1}; // 인접한 8개의 칸에 대한 col 좌표

while (!breeding_tree.empty()) { // 번식하는 나무 큐에 있는 모든 나무에 대해서
int row = breeding_tree.front().first; //번식할 나무의 행
int col = breeding_tree.front().second; //번식할 나무의 열
breeding_tree.pop(); // 정보 저장된 번식할 나무는 큐에서 제거

for (int j = 0; j < 8; j++) { // 8개의 칸에 대해
int nr = row + dr[j]; // row 위치 이동
int nc = col + dc[j]; // col 위치 이동
if (nr < 0 || nr >= n || nc < 0 || nc >= n) // 땅을 벗어나는 칸에는 나무 안 생김
continue; // 추가 과정 없이 계속
tree.push_front({1, nr, nc}); //새로 생긴 나무
}
}
}
// 겨울 - 땅을 돌아다니면서 양분 추가
void winter(int n, matrix &a, matrix &land) {
for (int i = 0; i < n; i++) // 상도의 모든 땅에
for (int j = 0; j < n; j++) // 가로, 세로 반복하면서
land[i][j] += a[i][j]; // 입력된 만큼의 양분 추가
}

/**
* [문제 설명] - 단순 구현 문제
* 봄: 하나의 칸마다 나이가 어린 나무부터 자신의 나이만큼 양분을 먹고, 나이가 1 증가함
* 각 칸에 양분이 부족해 자신의 나이만큼 양분을 못 먹는 나무는 즉시 죽음
* 여름: 봄에 죽은 나무가 양분으로 변함. 죽은 나무마다 나이를 2로 나눈 값이 양분으로 추가 (소수점 버림)
* 가을: 나이가 5의 배수인 나무가 번식. 인접한 8개 칸에 나이가 1인 나무가 생김
* 겨울: 로봇(S2D2)이 땅을 돌아다니면서 A[r][c]만큼 각 칸에 양분 추가
*
* K년이 지난 후 상도의 땅에 살아있는 나무의 개수
*
* [문제 풀이]
* a: 로봇(S2D2)가 겨울에 주는 양분의 양
* land: 땅의 양분
* breeding_tree: 나이가 5의 배수인 트리 (번식할 트리)
* tree: 땅에 심은 나무 나이, 행, 열 정보
* -> deque 컨테이너를 활용해 번식한 나무를 앞에 넣어주면 입력 후에만 정렬해서 사용 가능
*
* 문제의 설명대로 계절별 연산을 진행
*/

int main() {
int n, m, k, x, y, z;

//입력
cin >> n >> m >> k;
// n: 땅의 한 변의 길이, m: 나무 개수, k: k년이 지난 후
matrix a(n, vector<int>(n, 0)); // 추가되는 양분, 초기 값 0으로 설정
matrix land(n, vector<int>(n, 5)); //처음 양분 모든 칸에 5
queue<pair<int, int>> breeding_tree; //번식할 트리
deque<tp> tree; // 나무의 정보를 담을 덱 생성
for (int i = 0; i < n; i++) // 땅의 넓이만큼
for (int j = 0; j < n; j++) // 반복하면서
cin >> a[i][j]; // 추가되는 양분 정보 입력
while (m--) { // 나무 개수만큼 반복하면서
cin >> x >> y >> z; // 나무의 정보 - x, y: 나무의 위치, z: 나무의 나이
tree.emplace_back(z, x - 1, y - 1); //(0, 0)부터 시작하도록 구현하기위해 1을 빼준 인덱스에 접근
}

//연산
sort(tree.begin(), tree.end()); //어린 나이 순으로 정렬
while (k--) { // 나무의 개수만큼 반복하면서
queue<tp> dead_tree = spring(land, tree, breeding_tree); //봄이 지나고 죽은 나무
summer(dead_tree, land); // 여름: 양분을 추가할 죽은 나무 받아서 함수 수행
fall(n, tree, breeding_tree); // 가을: 나무 번식
winter(n, a, land); // 겨울: 땅에 양분 추가
}

//출력
cout << tree.size();
}
59 changes: 59 additions & 0 deletions 11월 30일 - 최소 신장 트리/1713_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Created by user on 2021-12-07.
//

#include <iostream>
#include <vector>
#include <map>

using namespace std;
typedef pair<int, int> ci;

map<int, ci>::iterator delCandidate(map<int, ci> &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; // 현재 후보가 추천 횟수가 가장 작다면 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;
// n: 사진틀의 개수
// m: 전체 학생의 총 추천 횟수

//입력 & 연산
cin >> n >> m;
map<int, ci> 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; // 후보 그냥 사진틀에 추가
candidate[input].first++; //추천 횟수 증가
}

//출력
for (auto iter = candidate.begin(); iter != candidate.end(); iter++) // 후보 리스트 크기만큼 반복하면서
cout << iter->first << ' ';
}
101 changes: 101 additions & 0 deletions 11월 30일 - 최소 신장 트리/1774_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// Created by user on 2021-12-07.
//

#include <iostream>
#include <vector>
#include <tuple>
#include <queue>
#include <cmath>

using namespace std;
typedef pair<double, double> ci;
typedef tuple<double, int, int> tp;

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; // 유니온 가능하다고 반환
}

double kruskal(int v, priority_queue<tp, vector<tp>, greater<>> &pq) {
int cnt = 0; // 사용한 간선 카운트
double sum = 0; // 간선의 가중치

while (cnt < v - 1) { //사용한 간선의 수가 v-1보다 적을 동안
double cost = get<0>(pq.top()); // 가중치 추가
int x = get<1>(pq.top()); // x의 좌표 저장
int y = get<2>(pq.top()); // y의 좌표 저장

pq.pop(); // 정보 입력한 간선 out
if (unionInput(x, y)) { //유니온 했다면
cnt++; //사용된 간선 증가
sum += cost; //간선의 가중치
}
}
return sum;
}

/**
* 4386번 : 별자리 만들기의 응용 문제
* 이미 연결된 정점들이 존재한다는 것을 제외하고는 4386번과 동일
*
* 1. 임의의 두 별에 대한 거리(간선) 모두 구하기
* 2. 이미 존재하는 통로들 표시
* !주의! 통로의 개수가 m개라면 v-m-1개의 간선만 더 추가하면 될까?
* 이미 연결된 통로들도 사이클을 이룰 수 있기 때문에 유니온 연산을 하며 사이클 없이 연결된 간선만 세기
* 3. 이미 연결된 통로의 수를 k개라고 하면 v-k-1개의 간선을 추가로 선택
*/
int main() {
int n, m, a, b, v = 0;
// n: 우주신들의 개수, m: 이미 연결된 통로의 수
priority_queue<tp, vector<tp>, greater<>> pq; // 우선순위 큐

//입력
cin >> n >> m;
parent.assign(n + 1, -1); // -1로 초기화
vector<ci> stars(n + 1); // 별 개수만큼 벡터 생성
for (int i = 1; i <= n; i++) // n개의 별에 대해
cin >> stars[i].first >> stars[i].second; // 우주신들의 좌표 적기


//연산
//임의의 두 별에 대한 거리(간선) 모두 구하기
for (int i = 1; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
double xd = stars[i].first - stars[j].first; //두 별사이 x축 거리
double yd = stars[i].second - stars[j].second; // 두 별 사이 y축 거리
pq.push({sqrt(xd * xd + yd * yd), i, j}); // 두 별 사이 거리를 큐에 추가
}
}
while (m--) { // 연결된 통로들에 대해
cin >> a >> b; // 통로 연결 정보 추가
if (unionInput(a, b)) //이미 연결된 통로
v++;
}

//연산 & 출력
cout << fixed;
cout.precision(2); // 소수점 둘째자리까지 출력
cout << kruskal(n - v, pq); // 최소의 통로 길이 출력
}
Loading

0 comments on commit 0482247

Please sign in to comment.