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

[최소 신장 트리] 12월 7일 -Update #28

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
86 changes: 86 additions & 0 deletions 1130/sample_16202.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include <iostream>
#include <vector>
#include <algorithm>
#include <tuple>

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

vector<int> parent; //부모 노드

//Find 연산
int findParent(int node) {
if (parent[node] < 0) //값이 음수면 루트 정점
return node; //해당 집합에 속하는 node 반환
return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기
}

//Union 연산
bool unionInput(int x, int y) { //x&y 유니온 연산
int xp = findParent(x); //x가 속한 집합 (부모노드)
int yp = findParent(y); //y가 속한 집합 (부모노드)

if (xp == yp) //같은 집합에 있다면 유니온 할 수 없음
return false; //유니온 불가
if (parent[xp] < parent[yp]) { //새로운 루트 xp
parent[xp] += parent[yp]; //xp의 집합 크기 증가
parent[yp] = xp; //yp의 집합이 xp의 집합으로 유니온
} else { //새로운 루트 yp
parent[yp] += parent[xp]; //yp의 집합 크기 증가
parent[xp] = yp; //xp의 집합이 yp의 집합으로 유니온
}
return true; //유니온 함
}

int kruskal(int n, int idx, vector<tp> &edges) { //정점 개수, 현재 턴, 정점 정보
int cnt = 0, sum = 0; // 사용 간선, 가중치 합
for (int i = idx; i < edges.size(); i++) { //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)) { //유니온 연산
cnt++; //간선의 수 +1
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<tp> edges; //재사용할거라 우선순위 큐가 아닌 벡터에 저장
for (int i = 1; i <= m; i++) { //i: 간선 번호
cin >> x >> y; //정점 저장
edges.emplace_back(i, x, y); //간선 정보 추가
}

bool flag = false; //MST 만들 수 있음
for (int i = 0; i < k; i++) { //k: 턴 차례
if (flag) { //더이상 mst를 만들 수 없음
cout << 0 << ' '; //만들 수 없음
continue; //다음 턴 검사
}
parent.assign(n + 1, -1); //초기화
int ans = kruskal(n, i, edges); //MST 비용
if (ans == 0) //0이면
flag = true; //mst 만들 수 없음
cout << ans << ' '; //이번 턴에서의 값 출력
}
}
117 changes: 117 additions & 0 deletions 1130/sample_16235.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#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); //양분 업데이트
}
}

void fall(int n, deque<tp> &tree, queue<pair<int, int>> &breeding_tree) { //행렬 크기, 트리 정보, 번식하는 나무 정보
int dr[8] = {-1, 1, 0, 0, -1, -1, 1, 1}; //상, 하, 좌, 우, 좌상향 대각선, 우상향 대각선, 좌하향 대각선, 우하향 대각선
int dc[8] = {0, 0, -1, 1, -1, 1, -1, 1};

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]; //차례로 이동
int nc = col + dc[j];
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++) //i: 행
for (int j = 0; j < n; j++) //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; //n*n 행렬, 심은 나무 개수, k년, x&y:나무 위치, 나무 나이

//입력
cin >> n >> m >> k;
matrix a(n, vector<int>(n, 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++) //i: 행
for (int j = 0; j < n; j++) //j: 열
cin >> a[i][j]; //양분
while (m--) { //나무 별로 정보 입력
cin >> x >> y >> z; //위치, 나이 정보
tree.emplace_back(z, x - 1, y - 1); //(0, 0)부터 시작하도록 구현하기위해 1을 빼준 인덱스에 접근
}

//연산
sort(tree.begin(), tree.end()); //어린 나이 순으로 정렬
while (k--) { //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();
}

54 changes: 54 additions & 0 deletions 1130/sample_1713.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#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; //가장 작은 추천 횟수 갱신
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<int, ci> candidate; //first: 후보 학생, second: {추천 횟수, 게시 시간}
for (int i = 0; i < m; i++) { //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 << ' '; //최종 후보 출력
}

97 changes: 97 additions & 0 deletions 1130/sample_1774.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#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; //해당 집합에 속하는 node 반환
return parent[node] = findParent(parent[node]); //그래프 압축하며 루트 정점 찾기
}

//Union 연산
bool unionInput(int x, int y) { //x&y 유니온 연산
int xp = findParent(x); //x가 속한 집합 (부모노드)
int yp = findParent(y); //y가 속한 집합 (부모노드)

if (xp == yp) //같은 집합에 있다면 유니온 할 수 없음
return false; //유니온 불가
if (parent[xp] < parent[yp]) { //새로운 루트 xp
parent[xp] += parent[yp]; //xp의 집합 크기 증가
parent[yp] = xp; //yp의 집합이 xp의 집합으로 유니온
} else { //새로운 루트 yp
parent[yp] += parent[xp]; //yp의 집합 크기 증가
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()); //시작 정점
int y = get<2>(pq.top()); //끝 정점

pq.pop(); //탐색한 정점 제거
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; //우주신들이 개수, 신들 사이 통로의 개수, 각 정점 좌표, 이미 연결된 통로
priority_queue<tp, vector<tp>, greater<>> pq; //간선 정보

//입력
cin >> n >> m; //정점 개수, 간선 개수
parent.assign(n + 1, -1); //정점 정보 초기화
vector<ci> stars(n + 1); //별(정점) 좌표 정보
for (int i = 1; i <= n; i++) //i: 별 번호
cin >> stars[i].first >> stars[i].second; //위치 입력


//연산
//임의의 두 별에 대한 거리(간선) 모두 구하기
for (int i = 1; i <= n - 1; i++) { //i: 시작별
for (int j = i + 1; j <= n; j++) { //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++; //연결된 간선 +1
}

//연산 & 출력
cout << fixed; //소수점 제한
cout.precision(2); //소수점 2자리까지 표현
cout << kruskal(n - v, pq); //최소 가중치 합 출력
}

Loading