From e0d99e4cd4156a9c85470bcdc2a63b757bce505e Mon Sep 17 00:00:00 2001 From: Inryu Date: Tue, 7 Dec 2021 22:40:48 +0900 Subject: [PATCH] =?UTF-8?q?[MST]=2012=EC=9B=94=207=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../16202.cpp" | 90 ++++++++++++++ .../16235.cpp" | 115 ++++++++++++++++++ .../1713.cpp" | 56 +++++++++ .../1774.cpp" | 102 ++++++++++++++++ .../21924.cpp" | 83 +++++++++++++ 5 files changed, 446 insertions(+) create mode 100644 "11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16202.cpp" create mode 100644 "11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16235.cpp" create mode 100644 "11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1713.cpp" create mode 100644 "11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1774.cpp" create mode 100644 "11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/21924.cpp" diff --git "a/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16202.cpp" "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16202.cpp" new file mode 100644 index 0000000..d8a52fa --- /dev/null +++ "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16202.cpp" @@ -0,0 +1,90 @@ +/* + * MST 게임 : https://www.acmicpc.net/problem/16202 + */ + +#include +#include +#include + +using namespace std; +typedef tuple tp; // 가중치, 두 정점의 번호 x,y + +vector 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 &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 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 비용 출력. + } +} \ No newline at end of file diff --git "a/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16235.cpp" "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16235.cpp" new file mode 100644 index 0000000..c870b62 --- /dev/null +++ "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/16235.cpp" @@ -0,0 +1,115 @@ + +/* + * 나무 재테크 : https://www.acmicpc.net/problem/16235 + */ +#include +#include +#include +#include +#include +#include + +using namespace std; +typedef vector> matrix; +typedef tuple tp; + +//봄 : 봄: 하나의 칸마다 나이가 어린 나무부터 자신의 나이만큼 양분을 먹고, 나이가 1 증가함 +//각 칸에 양분이 부족해 자신의 나이만큼 양분을 못 먹는 나무는 즉시 죽음 +queue spring(matrix &land, deque &tree, queue> &breeding_tree) { + queue 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 &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 &tree, queue> &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(n, 0)); // 겨울에 주는 양분의 양 + matrix land(n, vector(n, 5)); //맵 : 처음 양분 모든 칸에 5 + queue> breeding_tree; //번식할 트리 (나이가 5의 배수) + deque 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 dead_tree = spring(land, tree, breeding_tree); //봄이 지나고 죽은 나무 + summer(dead_tree, land); + fall(n, tree, breeding_tree); + winter(n, a, land); + } + + //출력 + cout << tree.size(); +} \ No newline at end of file diff --git "a/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1713.cpp" "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1713.cpp" new file mode 100644 index 0000000..32a5f31 --- /dev/null +++ "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1713.cpp" @@ -0,0 +1,56 @@ +/* + * 후보 추천하기 : https://www.acmicpc.net/problem/1713 + */ + +#include +#include + +using namespace std; +typedef pair ci; + +// 삭제할 후보 찾아서 return +map::iterator delCandidate(map &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 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 << ' '; //사진틀에 사진이 게재된 최종 후보의 학생 번호를 증가하는 순서대로 출력 +} \ No newline at end of file diff --git "a/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1774.cpp" "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1774.cpp" new file mode 100644 index 0000000..ad8d366 --- /dev/null +++ "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/1774.cpp" @@ -0,0 +1,102 @@ +/* + * 우주신과의 교감 : https://www.acmicpc.net/problem/1774 + */ + +#include +#include +#include +#include +#include + +using namespace std; +typedef pair ci; +typedef tuple tp; + +vector 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, 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, greater<>> pq; //min heap + + //입력 + cin >> n >> m; // 정점 갯수, 이미 연결된 엣지 개수 + parent.assign(n + 1, -1); //union& find용 초기화 + vector 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); //연결된 거 빼고! +} \ No newline at end of file diff --git "a/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/21924.cpp" "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/21924.cpp" new file mode 100644 index 0000000..0139e9c --- /dev/null +++ "b/11\354\233\224 30\354\235\274 MST \354\266\224\352\260\200\354\240\234\354\266\234/21924.cpp" @@ -0,0 +1,83 @@ +/* + * 도시 건설 : https://www.acmicpc.net/problem/21924 + */ + +#include +#include +#include +#include + +using namespace std; +typedef tuple tp; + +vector 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 +long long kruskal(int v, long long tot, priority_queue, greater<>> &pq) { + int cnt = 0; // 사용한 간선의 개수 + long long sum = 0; // mst 비용 + + while (cnt < v - 1) { //사용한 간선의 수가 v-1보다 적을 동안 + if (pq.empty()) //사용한 간선이 v-1개가 안됐는데 더 이상 검사할 간선이 없다면 + return -1; // 모든 건물을 다 연결 할 수 없으므로 -1 출력 + + //가중치가 작은 순서대로 들어가 있음 + int cost = get<0>(pq.top()); //가중치 + int x = get<1>(pq.top()); //정점1 + int y = get<2>(pq.top()); //정점2 + + pq.pop(); //간선 사용. + if (unionInput(x, y)) { //유니온 했다면 + cnt++; //사용한 간선 개수 ++ + sum += cost; //mst 비용에 가중치 더해주기 + } + } + return tot - sum; // (절약할 수 있는 예산) = (도로를 다 설치 할 때 드는 비용) - (MST 비용) +} + +/** + * 기본 MST 문제에서 트리를 만들 수 없는 경우(간선이 N-1개가 아닌 경우)를 고려한 문제 + * + * 최대 비용의 범위가 약 10^6 x 10^5 이므로 long long 자료형을 써야 함 + */ + +int main() { + int n, m, a, b, c; //건물(노드)개수, 도로(엣지)개수 + long long tot = 0; // 모든 도로 비용의 합 + priority_queue, greater<>> pq; // min heap + + //입력 + cin >> n >> m; + parent.assign(n + 1, -1); // union, find + while (m--) { // 간선 개수 + cin >> a >> b >> c; //두 정점 a,b 가중치 c + pq.push({c, a, b}); // 가중치를 기준으로 min heap이 되도록. + tot += c; //도로를 다 설치할 때 드는 비용 + } + //연산 & 출력 + cout << kruskal(n, tot, pq); +} \ No newline at end of file