From 66c4b65d6435e9fc587093518242bdf4daa7d943 Mon Sep 17 00:00:00 2001 From: MinjaeJo1999 <76865900+MinjaeJo1999@users.noreply.github.com> Date: Tue, 7 Dec 2021 18:01:26 +0900 Subject: [PATCH] =?UTF-8?q?[=EC=B5=9C=EC=86=8C=20=EC=8B=A0=EC=9E=A5=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC]=2012=EC=9B=94=207=EC=9D=BC=20-Update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1130/sample_16202.cpp | 86 +++++++++++++++++++++++++++++++ 1130/sample_16235.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++ 1130/sample_1713.cpp | 54 +++++++++++++++++++ 1130/sample_1774.cpp | 97 ++++++++++++++++++++++++++++++++++ 1130/sample_21924.cpp | 78 ++++++++++++++++++++++++++++ 5 files changed, 432 insertions(+) create mode 100644 1130/sample_16202.cpp create mode 100644 1130/sample_16235.cpp create mode 100644 1130/sample_1713.cpp create mode 100644 1130/sample_1774.cpp create mode 100644 1130/sample_21924.cpp diff --git a/1130/sample_16202.cpp b/1130/sample_16202.cpp new file mode 100644 index 0000000..af2fe9c --- /dev/null +++ b/1130/sample_16202.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +using namespace std; +typedef tuple tp; + +vector 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 &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 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 << ' '; //이번 턴에서의 값 출력 + } +} diff --git a/1130/sample_16235.cpp b/1130/sample_16235.cpp new file mode 100644 index 0000000..7ae99b9 --- /dev/null +++ b/1130/sample_16235.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +typedef vector> matrix; //행렬 +typedef tuple tp; //나무 정보 + +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; //죽은 나무 정보 반환 +} + +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(); //죽은나무 -> 양분 + land[row][col] += (age / 2); //양분 업데이트 + } +} + +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}; + + 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(n, 0)); //행렬 초기화 + matrix land(n, vector(n, 5)); //처음 양분 모든 칸에 5 + queue> breeding_tree; //번식할 트리 + deque 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 dead_tree = spring(land, tree, breeding_tree); //봄이 지나고 죽은 나무 + summer(dead_tree, land); //죽은 나무 여름에 양분으로 추가 + fall(n, tree, breeding_tree); //가을 나무 번식 + winter(n, a, land); //땅에 양분 추가 + } + + //출력 + cout << tree.size(); +} + diff --git a/1130/sample_1713.cpp b/1130/sample_1713.cpp new file mode 100644 index 0000000..6111e33 --- /dev/null +++ b/1130/sample_1713.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +using namespace std; +typedef pair ci; //추천 횟수&게시 기간 + +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++) { //후보자마다 검사 + 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++) { //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 << ' '; //최종 후보 출력 +} + diff --git a/1130/sample_1774.cpp b/1130/sample_1774.cpp new file mode 100644 index 0000000..bd72e7e --- /dev/null +++ b/1130/sample_1774.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + +using namespace std; +typedef pair ci; //좌표 정보 +typedef tuple tp; //간선 정보 + +vector 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, 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, greater<>> pq; //간선 정보 + + //입력 + cin >> n >> m; //정점 개수, 간선 개수 + parent.assign(n + 1, -1); //정점 정보 초기화 + vector 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); //최소 가중치 합 출력 +} + diff --git a/1130/sample_21924.cpp b/1130/sample_21924.cpp new file mode 100644 index 0000000..cbeee99 --- /dev/null +++ b/1130/sample_21924.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +using namespace std; +typedef tuple tp; //두 건물의 번호, 건물 간 도로 비용 + +vector 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) { + 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; //참 반환 +} + +long long kruskal(int v, long long tot, priority_queue, greater<>> &pq) { //건물 개수, 최대 비용, 간선 정보 + int cnt = 0; // 사용 간선 + long long sum = 0; //가중치 합, (범위 10^6*10^5) + + 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()); //시작건물 + int y = get<2>(pq.top()); //끝건물 + + pq.pop(); //탐색한 간선 제거 + if (unionInput(x, y)) { //유니온 했다면 + cnt++; //사용 간선 +1 + sum += cost; //가중치 업데이트 + } + } + return tot - sum; //절약 비용 (최대 비용 - 최소 비용) +} + +/** + * 기본 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; //간선 정보 큐 + + //입력 + cin >> n >> m; + parent.assign(n + 1, -1); //정점 정보 초기화 + while (m--) { //간선 별로 + cin >> a >> b >> c; //정보 저장 + pq.push({c, a, b}); //간선 정보 큐에 삽입 + tot += c; //도로를 다 설치할 때 드는 비용 + } + + //연산 & 출력 + cout << kruskal(n, tot, pq); +}