Skip to content

Commit

Permalink
[최소 신장 트리] 11월 23일 -ing
Browse files Browse the repository at this point in the history
[최소 신장 트리] 11월 23일 -ing
  • Loading branch information
spiritstone authored Jan 7, 2022
2 parents 4648f0b + 0482247 commit 86ac6a3
Show file tree
Hide file tree
Showing 7 changed files with 545 additions and 0 deletions.
65 changes: 65 additions & 0 deletions 11월 30일 - 최소 신장 트리/1197_prim_re.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// Created by user on 2021-12-06.
//

//
// Created by iw040 on 2021/11/30.
//

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

using namespace std;
const int INF = 1e6;
typedef pair<int, int> ci;

int prim(int v, int start, vector<vector<ci>> &graph) {
int sum = 0; // 가중치 변수
//vector<int> dist(v + 1, INF); //각 정점까지의 비용 (없어도 상관없으나, 사용하면 메모리를 좀 더 아낄 수 있음)
vector<bool> visited(v + 1, false); //정점 방문 여부 (다익스트라와 달리 프림에선 필요!)
priority_queue<ci, vector<ci>, greater<>> pq; // 우선순위큐 선언

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

while (!pq.empty()) { // 큐가 비어있지 않을 동안
int weight = pq.top().first; //간선 가중치
int node = pq.top().second; //현재 정점
pq.pop(); // 맨 위에 있는 값 out

if (visited[node]) //이미 확인했던 정점
continue; // pass
sum += weight; //MST 간선 가중치 총 합에 더해줌
visited[node] = true; //방문 처리

for (int i = 0; i < graph[node].size(); i++) { // 그래프의 크기 동안 반복
int next_node = graph[node][i].first; // 다음 정점
int next_weight = graph[node][i].second; // 다음 간선 가중치
if (!visited[next_node]) { //미방문 정점(이면서 더 짧은 간선을 통해 갈 수 있다면)
//dist[next_node] = next_weight;
pq.push({next_weight, next_node}); // 큐에 삽입
}
}
}
return sum; // 트리의 가중치 반환
}

int main() {
int v, e, a, b, c;
// v: 정점의 개수, e: 간선의 개수
// a, b, c: 간선에 대한 정보

//입력
cin >> v >> e; // 정점과 간선의 개수 입력
vector<vector<ci>> graph(v + 1, vector<ci>(0)); // 정점 개수만큼의 크기를 가진 그래프 생성
while (e--) { //무방향 그래프
cin >> a >> b >> c; // 간선 정보 입력
graph[a].emplace_back(b, c); // a번 정점과 b번 정점이 가중치 c인 간선으로 연결
graph[b].emplace_back(a, c); // b번 정점과 a번 정점이 가중치 c인 간선으로 연결
}

//연산 & 출력
cout << prim(v, 1, graph);
}
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 << ' ';
}
Loading

0 comments on commit 86ac6a3

Please sign in to comment.