Skip to content
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월 30일 MST 추가제출/16202.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* MST 게임 : https://www.acmicpc.net/problem/16202
*/

#include <iostream>
#include <vector>
#include <tuple>

using namespace std;
typedef tuple<int, int, int> tp; // 가중치, 두 정점의 번호 x,y

vector<int> parent; // Union Find

//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에 자식 하나 더해주기
parent[yp] = xp; //yp의 부모는 xp
} else { //새로운 루트 yp
parent[yp] += parent[xp]; //yp에 자식 하나 더해주기
parent[xp] = yp; //xp의 부모는 yp
}
return true; //유니온 했을 경우 true 리턴.
}

// kruskal
int kruskal(int n, int idx, vector<tp> &edges) {
int cnt = 0, sum = 0; //사용한 간선의 수, mst 비용
for (int i = idx; i < edges.size(); i++) { //idx-1까지의 간선은 제외하고
if (cnt == n - 1) //n-1개의 간선을 모두 연결함
break;
int dist = get<0>(edges[i]); //가중치
int x = get<1>(edges[i]); //정점1 번호
int y = get<2>(edges[i]); //정점2 번호

if (unionInput(x, y)) { //유니온 했을 경우
cnt++; // 사용한 간선의 수 ++
sum += dist; // mst 비용에 가중치 더해주기
}
}
if (cnt < n - 1) //mst를 만들 수 없으면 0 리턴
return 0;
return sum; // mst 만들 수 있으면 mst 비용 리턴
}

/**
* 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++) { //간선 정보
cin >> x >> y; // 두 정점의 번호
edges.emplace_back(i, x, y); //가중치, 두 정점의 번호
}

bool flag = false; // mst를 만들지 못한 상황인가
for (int i = 0; i < k; i++) {
if (flag) { //더이상 mst를 만들 수 없음
cout << 0 << ' '; //0 출력
continue;
}
parent.assign(n + 1, -1); //초기화
int ans = kruskal(n, i, edges); //i-1까지의 간선은 제외. //mst 비용 구하기
if (ans == 0) //mst 만들 수 없다면
flag = true; //flag값 설정해주고 이후 것들도 모두 0
cout << ans << ' '; // mst 비용 출력.
}
}
115 changes: 115 additions & 0 deletions 11월 30일 MST 추가제출/16235.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

/*
* 나무 재테크 : https://www.acmicpc.net/problem/16235
*/
#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;

//봄 : 봄: 하나의 칸마다 나이가 어린 나무부터 자신의 나이만큼 양분을 먹고, 나이가 1 증가함
//각 칸에 양분이 부족해 자신의 나이만큼 양분을 못 먹는 나무는 즉시 죽음
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; //죽은 나무 리턴
}

//여름: 봄에 죽은 나무가 양분으로 변함. 죽은 나무마다 나이를 2로 나눈 값이 양분으로 추가 (소수점 버림)
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(); // pop해서 없애고
land[row][col] += (age / 2); //양분으로 변해서 원래 있던 자리에 추가
}
}

// 가을: 나이가 5의 배수인 나무가 번식. 인접한 8개 칸에 나이가 1인 나무가 생김
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};

// 나이가 5의 배수인 나무 저장했던 것
while (!breeding_tree.empty()) {
int row = breeding_tree.front().first; //번식할 나무의 행
int col = breeding_tree.front().second; //번식할 나무의 열
breeding_tree.pop(); //pop해서 없애줘야 함

//8방향으로
for (int j = 0; j < 8; j++) {
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}); //새로 생긴 나무 -> 나이가 1이므로 front에 Push!
}
}
}

// 겨울: 로봇(S2D2)이 땅을 돌아다니면서 A[r][c]만큼 각 칸에 양분 추가
void winter(int n, matrix &a, matrix &land) {
//land를 돌아다니며 각 칸에 양분 추가 (a)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
land[i][j] += a[i][j];
}

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

//입력
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; //번식할 트리 (나이가 5의 배수)
deque<tp> tree; // 번식한 나무 (나이가 1)인 애를 앞에 넣어주면 입력 후에만 정렬 한 후 사용 가능.

//겨울에 주는 양분의 양 입력
for (int i = 0; i < n; i++)
for (int j = 0; j < n; 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()); //어린 나이 순으로 정렬
//k년 동안 반복
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();
}
56 changes: 56 additions & 0 deletions 11월 30일 MST 추가제출/1713.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 후보 추천하기 : https://www.acmicpc.net/problem/1713
*/

#include <iostream>
#include <map>

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

// 삭제할 후보 찾아서 return
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++) { //candidate 맵을 순차 탐색
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++) {
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 << ' '; //사진틀에 사진이 게재된 최종 후보의 학생 번호를 증가하는 순서대로 출력
}
102 changes: 102 additions & 0 deletions 11월 30일 MST 추가제출/1774.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 우주신과의 교감 : https://www.acmicpc.net/problem/1774
*/

#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; //union & find 용 루트 노드 저장.

//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의 자식 늘려주기
parent[yp] = xp; //yp의 부모는 xp
} else { //새로운 루트 yp
parent[yp] += parent[xp]; //yp의 자식 늘려주기
parent[xp] = yp; //xp의 부모는 yp
}
return true; //유니온 할 수 있다면 true 리턴
}

//kruskal
double kruskal(int v, priority_queue<tp, vector<tp>, greater<>> &pq) {
int cnt = 0; //사용한 간선 개수
double sum = 0; //mst 비용

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

pq.pop(); //pq에서 pop 해주기
if (unionInput(x, y)) { //유니온 했다면
cnt++; //사용된 간선 증가
sum += cost; //간선의 가중치 더해주기
}
}
return sum; //mst 비용 리턴
}

/**
* 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; //min heap

//입력
cin >> n >> m; // 정점 갯수, 이미 연결된 엣지 개수
parent.assign(n + 1, -1); //union& find용 초기화
vector<ci> stars(n + 1); // 우주신들의 좌표
for (int i = 1; i <= n; i++)
cin >> stars[i].first >> stars[i].second; //우주신들의 좌표

//연산
//통로들의 길이는 2차원 좌표계상의 거리와 같다.
//임의의 두 별에 대한 거리(간선) 모두 구하기
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}); // 거리(가중치), 좌표1, 좌표2
}
}
// 이미 연결된 엣지
while (m--) {
cin >> a >> b; ///이미 연결된 통로
if (unionInput(a, b)) //이미 연결된 통로 union 가능하면 (사이클 안 생김)
v++; // 사이클 없이 이미 연결된 간선
}

//연산 & 출력
cout << fixed;
cout.precision(2); //소수점 둘째자리까지 출력
cout << kruskal(n - v, pq); //연결된 거 빼고!
}
Loading