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

[동적 계획법과 최단 거리 역추적] 11월 16일 #18

Open
wants to merge 1 commit into
base: master
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
66 changes: 66 additions & 0 deletions 11월 9일 추가제출/12852.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 1로 만들기 2 : https://www.acmicpc.net/problem/12852
*/

#include <iostream>
#include <vector>

using namespace std;

//역추적
vector<int> back(int x, vector<int> &path) { //x = n부터 역추적 시작
vector<int> result(0); // 역추적한 루트 저장
while (x != 0) { //맨 처음 시작 경로가 나올 때 까지
result.push_back(x); // 역추적한 값 넣고
x = path[x]; //x는 다음 패스값으로 갱신
}
return result; //역추적 루트 리턴
}

//1로 만드는 최소 연산 횟수 리턴
int makeOne(int n, vector<int> &path) {
vector<int> dp(n + 1, 0); //dp용 배열 dp[i] = (i에 가능한 연산을 적용한 수 중 최소 연산 횟수) + 1
for (int i = 2; i <= n; i++) { //dp 1~n까지!! 1부터 시작해서 n까지 간다.
int min_value = dp[i - 1]; //우선 i - 1 연산 적용한 것으로 최소 연산 횟수 저장
path[i] = i - 1; //i-1 연산 적용한 것으로 생각하고 바로 1 작은 수
if (!(i % 3) && min_value > dp[i / 3]) { //3으로 나눌 수 있고, 현재 가정해 놓은 최소 연산 회수보다 더 적게 가능하면
min_value = dp[i / 3]; //min_value 갱신
path[i] = i / 3; //path 갱신
}
if (!(i % 2) && min_value > dp[i / 2]) { //2 나눌 수 있고, 현재 가정해 놓은 최소 연산 회수보다 더 적게 가능하면
min_value = dp[i / 2]; //min_value 갱신
path[i] = i / 2;//path 갱신
}
dp[i] = min_value + 1; // min_value에서 연산 하나 적용해서 바로 온 거니깐. min_value+1
}
return dp[n]; //n에 가능한 연산을 적용한 수 중 최소 연산 횟수
}

/**
* 기본 문제: 1로 만들기
*
* [점화식]
* dp[i] = (i에 가능한 연산을 적용한 수 중 최소 연산 횟수) + 1
* dp[i] = min(dp[i / 3], dp[i / 2], dp[i - 1]) + 1
*
* [역추적]
* path: 인덱스가 정수를 나타냄, 해당 수에서 연산을 적용한 다음 수를 저장
* n부터 역추적 시작
*/

int main() {
int n;
//입력
cin >> n;
vector<int> path(n + 1, 0); //경로 저장

//연산
int ans = makeOne(n, path); //n을 1로 만드는 최소 연산 횟수 //path에 연산 적용한 수 계속 쌓으면서
vector<int> result = back(n, path); // path를 n부터 역추적해서 벡터에 담기.

//출력
cout << ans << '\n'; //최소 연산 횟수
for (int i = 0; i < result.size(); i++) //역추적한 경로
cout << result[i] << ' ';
return 0;
}
68 changes: 68 additions & 0 deletions 11월 9일 추가제출/1719.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 택배 : https://www.acmicpc.net/problem/1719
*/
#include <iostream>
#include <vector>

using namespace std;
const int INF = 1e5 * 2; //최대 n-1개의 간선을 지나게 됨

//플로이드-워셜
void floydWarshall(int n, vector<vector<int>> &graph, vector<vector<int>> &table) {
for (int k = 1; k <= n; k++) { //k : 거치는 노드
for (int i = 1; i <= n; i++) { //2차원 배열 모두 살피면서 (각 노드에서 각 노드로)
for (int j = 1; j <= n; j++) {
int new_dist = graph[i][k] + graph[k][j]; //중간에 k를 거쳐서 i에서 j로 감
if (new_dist < graph[i][j]) { //i->k->j가 i->j보다 빠른 경로라면
graph[i][j] = new_dist; //i ->j로 가는 경로 갱신
table[i][j] = table[i][k]; //i-> j로 가기 위해 제일 먼저 거쳐야 하는 정점은 i->k로 가기 위해 제일 먼저 거쳐야 하는 정점!!
}
}
}
}
}

/**
* graph : 플로이드-워셜 결과 행렬 그래프
* table : 경로표. table[i][j] = i->j로 가기 위해 제일 먼저 거쳐야 하는 정점
*
* 1. i->j의 중간 경로를 i로 초기화
* 2. i->k->j가 i->j보다 짧다면 i->j의 중간 경로를 i->k의 중간 경로(table[i][k])로 갱신
* k로 갱신하는게 아니라 table[i][k]로 갱신하는 이유는?
* 만약 i->k의 경로가 i->t->k라면 최종 경로는 i->t->k->j
* 바로 k로 갱신하면 t를 놓칠 수 있기 때문에 table[i][k]로 갱신
* line 16을 table[i][j] = k로 바꾸면 결과가 어떻게 되는지 확인해보세요
*/
int main() {
int n, m, s, d, c;
//입력
cin >> n >> m; //집하장의 개수(노드), 집하장간 경로 개수(엣지)
vector<vector<int>> graph(n + 1, vector<int>(n + 1, INF)); //플로이드-워셜 결과 행렬 그래프
vector<vector<int>> table(n + 1, vector<int>(n + 1, 0)); //경로표. table[i][j] = i->j로 가기 위해 제일 먼저 거쳐야 하는 정점
for (int i = 1; i <= n; i++)
graph[i][i] = 0; //자기자신

while (m--) { //무방향 그래프
cin >> s >> d >> c;
//간선 정보
graph[s][d] = graph[d][s] = c; // s->d(d->s)로 가는데 가중치 c

//경로 정보
table[s][d] = d; //s->d로 가기 위해 제일 먼저 거쳐야 할 정점
table[d][s] = s; //d->s로 가기 위해 제일 먼저 거쳐야 할 정점
}

//연산
floydWarshall(n, graph, table); //플로이드-워샬 계산, table에 경로 저장

//출력
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) //자기 자신
cout << "- ";
else //경로 출력
cout << table[i][j] << ' ';
}
cout << '\n';
}
}
81 changes: 81 additions & 0 deletions 11월 9일 추가제출/9019.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* DSLR : https://www.acmicpc.net/problem/9019
*/

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

using namespace std;
typedef pair<int, char> ci; // {숫자, 명령어}
const int SIZE = 10000; //A와 B는 모두 10,000 미만

//역추적
string back(int x, vector<ci> &path) { //x = B부터 역추적 시작
string ans = "";
while (path[x].first != -1) { //맨 처음 넣어둔 시작 경로가 될 때까지
ans += path[x].second; // 명령어 역추적해서 앞에서부터 쌓음
x = path[x].first; //숫자 갱신
}
reverse(ans.begin(), ans.end()); // 명령어 역추적해서 앞에서부터 쌓았으므로 리버스 해줌
return ans; // 역추적한 명렁어 리턴
}

//bfs
void bfs(int a, int b, vector<ci> &path) {
vector<bool> visited(SIZE, false); //이미 썼던 숫자인지.
queue<int> q; //큐에 노드(숫자값) 저장
q.push(a); //시작 노드 초기화
visited[a] = true; //시작 노드(숫자값) 초기화
while (!q.empty()) { //bfs 탐색 진행
int cur = q.front(); //현재 수
q.pop(); // pop 안 하면 무한루프!

if (cur == b) //B를 만들었다면 즉시 탐색 종료
break;

//차례대로 D,S,L,R 명령어를 실행했을 때 path (first : 경로(숫자), second : 명령어)
vector<ci> child = {{cur * 2 % SIZE, 'D'}, //각 명렁어의 수행 결과를 사칙연산으로 경로 나타낼 수 있음
{(cur - 1 + SIZE) % SIZE, 'S'},
{(cur * 10 % SIZE) + (cur / 1000), 'L'},
{(cur % 10 * 1000) + (cur / 10), 'R'}};

//각 명령어를 모두 본다.
for (int i = 0; i < 4; i++) {
int next = child[i].first; //다음 숫자.
if (!visited[next]) { //방문한 적 없으면
q.push(next); // 큐에 추가해주고
visited[next] = true; //방문 체크
path[next] = ci(cur, child[i].second); //자식 노드에 부모 노드(경로)와 명령어 저장
}
}
}
}

/**
* path: 경로와 수행한 명령어(D, S, L, R)를 저장
*
* 각 명령어의 수행 결과를 사칙연산으로 나타낼 수 있음 (cur: 현재 수, SIZE = 10,000)
* D: cur * 2 % SIZE
* S: (cur - 1 + SIZE) % SIZE (cur이 0일 경우를 처리하기 위해)
* L: (cur * 10 % SIZE) + (cur / 1000)
* R: (cur % 10 * 1000) + (cur / 10)
*
* 따라서 위의 연산을 적용한 값들을 자식노드로 하여 bfs 탐색 진행
* 앞서 구한 path값을 따라 B인덱스부터 역추적 시작
*/

int main() {
int t, a, b;

//입력 & 연산 & 출력
cin >> t; //테스트케이스
while (t--) {
cin >> a >> b;
vector<ci> path(SIZE, {-1, ' '}); //first: 경로, second: 명령어
bfs(a, b, path); //a -> b 경로 bfs 실행
cout << back(b, path) << '\n'; // 최단 경로 역추적!
}
return 0;
}