https://www.acmicpc.net/problem/1753

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가

www.acmicpc.net

해당문제는 다익스트라 알고리즘을 Heap을 이용하여 구현하는 문제이다.

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

using namespace std;

vector<pair<int, int>> matrix[20005];
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

int result[20005];
int INF = 999999;


void dijkstra(int start) {
	result[start] = 0;
	pq.push(make_pair(0, start));

	while (!pq.empty()) {
		int cur = pq.top().second;
		int cost = pq.top().first;
		pq.pop();

		int size = matrix[cur].size();
		for (int i = 0; i < size; i++) {
			int next = matrix[cur][i].second;
			int newCost = matrix[cur][i].first + cost;
			if (result[next] > newCost) {
				result[next] = newCost;
				pq.push(make_pair(newCost, next));
			}
		}
	}
}

int main(void) {
	int n, e;
	cin >> n >> e;

	/* init cost array */
	for (int i = 0; i <= n; i++)
		result[i] = INF;

	int start;
	cin >> start;

	for (int i = 0; i < e; i++) {
		int u, v, c;
		cin >> u >> v >> c;
		matrix[u].push_back(make_pair(c,v));
	}

	dijkstra(start);

	for (int i = 1; i <= n; i++) {
		if (result[i] == INF) cout << "INF\n";
		else cout << result[i] << "\n";
	}
}

다익스트라 알고리즘은 한 점에서부터 다른 노드로의 경로의 최소 값을 구하는 알고리즘이다.

 

다익스트라의 과정으로는 다음과 같다.

 

1. 시작 노드는 비용을 0으로 설정한다.

2. 시작 노드를 기준으로 비용 초기값을 설정한다. (인접한 값은 해당 가중치로, 인접하지 않았다면 무한대로)

3. 비용값 중 방문하지 않았고 비용 중 가장 작은 값을 다음 노드로 선택, 방문한다.

4. 선택된 노드까지의 비용 + 선택된 노드에서 인접한 노드로의 경로 비용 vs 인접한 노드의 비용을 비교하여 더 값싼 비용을 선택, 인접 노드로의 경로 비용을 갱신한다.

5. 3번으로 돌아가 반복한다.

 

여기서 3~4번의 과정을 효율적으로 수행하기 위하여 Heap 구조를 사용한다.

 

* priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

 

priority_queue의 경우 기본적으로 less, 즉 내림차순 정렬이다.

위와 같은 선언을 통하여 Min Heap으로 선언 가능하다.

 

* #include<utility>의 pair를 활용하여 인접 리스트 방식으로 구현이 가능하다.

* pair를 활용한 priority_queue의 비교 순서는 first, second 순서로 비교한다.

 

//가져갈 수 있는 돌의 수가 1개 3개일 경우
//9655 돌 게임1

#include<iostream>

using namespace std;

int main(void){
        int n;
        cin >> n;

        int SK[1000] = {-1, 1, 0, 1};

        int result;
        for(int i=4; i<=n; i++)
                SK[i]  = !(SK[i-3] && SK[i-1]);

        if(SK[n])
                cout<<"SK\n";
        else
                cout<<"CY\n";
        return 0;
}

https://www.acmicpc.net/problem/9655

 

9655번: 돌 게임

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

www.acmicpc.net

https://www.acmicpc.net/problem/9657

 

9657번: 돌 게임 3

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

www.acmicpc.net

 

0. 개요

처음 이걸 접했을 때 참 많은 생각이 들었다.

 

1개일땐 이런 경우..

2개일땐 이런 경우..

그러다 수가 점점 커지면..

 

9개일땐 8개 or 5개의 경우의수,

그렇다면 거기서 또 파생되는 경우의수....

 

이 개념을 이해하기 힘들었는데 조금이나마 이 포스팅을 보게 될 사람들이 쉽게 이해할 수 있다면 좋겠다.

 

1. 문제 설명

n개의 돌이 있고, 각각 번갈아 가며 1개, 3개(혹은 4개까지) 가지고 갈 수 있다.

마지막에 돌을 가지고 가는 사람이 이기는 게임이다.

 

이 또한 동적 계획법을 사용한다.

동적 계획법의 가장 큰 장점은 이전에 미리 구해놓은 최적해는 더 생각 안하고 가져다 쓸 수 있다는 점이다.

그것을 이용해서 문제를 풀어볼것이다.

 

남은 돌의 수, 현재 플레이어, 다음 플레이어 순서로 생각을 해본다면 다음과 같아진다.

1 W L
2 L W
3 W L
4 L W
5 W L
6 L W
... ... ...

일단 가장 중요한 것은 빨간색 까지는 우리가 구할 수 있다.

돌이 1개가 남았다면 현재 플레이어가 가져간다면 다음 플레이어는 지게 된다.

돌이 2개가 남았다면 현재 플레이어가 가져간다면 다음 플레이어가 남은 한개를 가져가 다음 플레이어가 승리한다.

돌이 3개가 남았다면 현재 플레이어가 모두 가져가 승리할 수 있다.

 

여기서 처음에 표를 완성할 때 조차 헷갈렸다.

그래서 이렇게 생각을 해봤다.

1 W
2 L
3 W
4 L
5 W
6  
... ...

 

표를 이렇게만 딱 놓고 생각해봤다.

현재 남은 돌의 갯수에 대한 승패이다.

지금 구해야 하는 것은 내 턴에 6개가 남았을 때.

여기서 내가 생각한 관점은 "상대에게 지는 수를 줄 수 있는가?" 이다.

즉, 6에서 1, 3을 가져간다면 다음 상대는 5 또는 3의 수를 받을 것이다.

 

5와 3은 모두 승리하는 경우다.

즉, 상대에게 지는 수를 줄 수 없으므로 내가 패배한다.

 

 

1 W L
2 L W
3 W L
4 W L
5    
6    
... ... ...

다음은 1, 3 그리고 4개까지 돌을 가지고 갈 수 있는 경우이다.

2번의 경우를 제외하고는 나머지 돌을 다 들고가버리면 이길 수 있다.

이것 또한 상대에게 지는 수를 주는것으로 생각해보자.

 

1 W
2 L
3 W
4 W
5  
6  
... ...

 

지금 채워야 하는 것은 5.

내가 상대에게 줄 수 있는 수는 1, 2, 4개가 남은 상황.

2번에 상대가 지는 수가 존재한다.

따라서 5개의 상황에서 내가 3개를 가져간다면 상대는 지게 되는 것이다.

 

그렇다면 5번은 W로 채워지겠고,

남은 돌이 6개일 경우에도 2, 3, 5개가 남은 경우를 줄 수 있는데, 2개가 남을경우 지는 수이므로 4개를 가져가 승리할 수 있다.

따라서 6번도 W으로 채워질 것이다.

 

2. 소스코드

//가져가는 경우가 1개, 3개 두가지
//9655 돌 게임1

#include<iostream>

using namespace std;

int main(void){
        int n;
        cin >> n;

        int SK[1000] = {-1, 1, 0, 1};

        int result;
        for(int i=4; i<=n; i++)
                SK[i]  = !(SK[i-3] && SK[i-1]);

        if(SK[n])
                cout<<"SK\n";
        else
                cout<<"CY\n";
        return 0;
}

 

//가져가는 경우가 1개, 3개, 4개일 경우의 코드
//9657 돌 게임3

#include<iostream>

using namespace std;

int main(void){
        int SK[1000] = {-1, 1, 0, 1, 1};

        int n;
        cin >> n;

        for(int i=5; i<=n; i++)
                SK[i] = !(SK[i-1]&&SK[i-3]&&SK[i-4]);

        if(SK[n])
                cout<<"SK";
        else
                cout<<"CY";
        return 0;
}

 

3. 여담

참고로 위에서 완성된 배열을 출력해보면 첫 번째 문제는 0, 1, 0, 1....

두번째 문제 또한 특정 패턴을 반복한다.

이를 이용해서 문제를 해결할 수도 있다.

https://www.acmicpc.net/problem/1181

 

1181번: 단어 정렬

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

www.acmicpc.net

0. 개요

 

이번 문제의 조건은 다음과 같습니다.

1. 길이가 짧은 것 부터

2. 길이가 같으면 사전순으로

3. 여러번 입력된 단어는 한 번만 출력한다

 

길이가 같으면 사전순으로 정렬하는 것은 sort 함수가 처리 해 줄테니 1, 3번만 고려하면 되겠습니다.

 

1. 소스 코드

#include<iostream>
#include<algorithm>
#include<string>

using namespace std;

bool compare(string a, string b) {
	if (a.length() == b.length()) return a < b;
	else return a.length() < b.length();
}

int main(void) {
	string arr[200000];
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> arr[i];

	sort(arr, arr + n, compare);

	for (int i = 0; i < n; i++){
		cout << arr[i] << "\n";
		while (!arr[i].compare(arr[i + 1])) 
			i++;
		
	}
	return 0;
}

 

1. compare 함수를 통하여 길이가 짧은 순으로, 길이가 같으면 사전순으로 먼저 정렬해줍니다.

2. 중복되는 문자열의 경우 다음번의 문자열과 비교하여 같다면 반복에 사용되는 변수 i를 1 증가하므로써 건너뛰는 효과를 주어 해결했습니다.

 

2. 마무리

정렬과 같이 저명한 파트는 이미 구현된 훌륭한 알고리즘이 너무 많습니다.

따라서 문제 풀이 시간도 자연스럽게 줄어들었습니다.

'백준 온라인 저지 - 단계별로 풀어보기 > 정렬' 카테고리의 다른 글

11650 - 좌표 정렬하기  (0) 2021.08.24
2750 - 수 정렬하기  (0) 2021.01.10

https://www.acmicpc.net/problem/11650

 

11650번: 좌표 정렬하기

첫째 줄에 점의 개수 N (1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에는 i번점의 위치 xi와 yi가 주어진다. (-100,000 ≤ xi, yi ≤ 100,000) 좌표는 항상 정수이고, 위치가 같은 두 점은 없다.

www.acmicpc.net

0. algorithm 라이브러리 활용

다양한 정렬 알고리즘을 구현할 수 있는 것도 매우 중요한 일이지만, 

이미 구현된 훌륭한 라이브러리를 활용하는 것도 하나의 방법입니다.

 

C++의 algorithm 라이브러리에 있는 sort 함수를 활용하여 문제를 푸는 시간을 가져보았습니다.

 

일반적인 사용 방법은 다음과 같습니다.

 

sort(시작, 끝)

 

위와같이 활용할 경우 범위 내의 요소들이 오름차순으로 정렬됩니다.

다만 sort 함수의 세번째 옵션에서 직접 정렬 기준을 만들어 낼 수 있습니다.

 

bool compare(int a, int b){
	return a < b;
}

위가 default값으로 되어있는 sort 함수의 compare 함수입니다.

a < b 즉, 왼쪽값이 작은 값이 되도록 되어있습니다.

부호의 방향을 바꾼다면, 내림차순으로 구현이 되겠죠.

이를 활용하여 문제를 해결해보겠습니다.

 

1. 소스코드

#include<iostream>
#include<algorithm>

using namespace std;

typedef struct _pos {
	int x;
	int y;
}pos;

int n;
pos arr[100000];

bool compare(pos a, pos b) {
	if (a.x == b.x) return a.y < b.y;
	else return a.x < b.x;
}


int main(void) {
	cin >> n;

	for (int i = 0; i < n; i++) 
		cin >> arr[i].x >> arr[i].y;
	
	sort(arr, arr + n, compare); //세번째 옵션으로 함수 이름을 넣는다.

	for (int i = 0; i < n; i++)
		cout << arr[i].x << " " << arr[i].y << "\n";
	return 0;
}

 

sort 함수가 호출되는 부분을 보시게 되면 compare()함수를 호출하여 인자를 넣는 것이 아닌 함수명을 인수로 넣어주는 모습을 확인할 수 있습니다.

 

구현은 간단하게 x값이 같다면 y값을 기준으로 정렬하도록 구현했습니다.

 

2. 정리

다양한 정렬 기법을 구현할 수 있는 것도 중요합니다.

하지만 이미 만들어진 훌륭한 라이브러리를 활용할 줄 아는 것도 하나의 능력이라 생각이 됩니다.

'백준 온라인 저지 - 단계별로 풀어보기 > 정렬' 카테고리의 다른 글

1181 - 단어 정렬  (0) 2021.08.24
2750 - 수 정렬하기  (0) 2021.01.10

https://www.acmicpc.net/problem/11724

 

11724번: 연결 요소의 개수

첫째 줄에 정점의 개수 N과 간선의 개수 M이 주어진다. (1 ≤ N ≤ 1,000, 0 ≤ M ≤ N×(N-1)/2) 둘째 줄부터 M개의 줄에 간선의 양 끝점 u와 v가 주어진다. (1 ≤ u, v ≤ N, u ≠ v) 같은 간선은 한 번만 주

www.acmicpc.net

 

 

0. 연결 요소란 무엇인가?

 

그래프에서 연결요소란 위의 사진과 같은 것입니다.

영어로는 Connected Component로 표현합니다.

위와 같은 형태를 글로 표현하잠면 아래와 같습니다.

 

1. 연결 요소의 노드의 쌍은 경로로 서로 연결 되어 있다. (노드 A에서 노드 B로 가는 길이 존재한다.)

2. 각각의 연결요소에서 다른 연결 요소로 가는 경로는 없다.(노란색 집합에서 녹색 집합으로 연결된 경로가 없다.)

 

구하는 방법은 의외로 간단한데, DFS나 BFS를 진행하며 더이상 탐색을 할 수 없을 경우 하나의 연결 요소를 찾게 됩니다.

모든 정점에 대해서 DFS 혹은 BFS를 진행하게 된다면 모든 연결요소를 찾을 수 있게 됩니다.

 

1. 소스코드

#include<iostream>

using namespace std;

int matrix[1001][1001] = { 0, };
int visit[1001] = { 0, };
int n, m;
int cnt = 0;
int dfs(int cur) {
	if (visit[cur] == 1) return 0;
	else visit[cur] = 1;
	for (int i = 1; i <= n; i++) {
		//연결되었다면
		if (matrix[cur][i] == 1 && visit[i] != 1) {
			dfs(i);
		}
	}
	return 1;
}

int main(void) {
	cin >> n >> m;

	int a, b;
	for (int i = 0; i < m; i++) {
		cin >> a >> b;
		matrix[a][b] = 1;
		matrix[b][a] = 1;
	}

	for (int i = 1; i <= n; i++) {
		if(dfs(i)) cnt++;
	}
	cout << cnt << "\n";
	return 0;
}

 

2. 마무리

다양한 용어들이 많은 이론인 만큼 개념을 확실히 정립해야 하겠습니다.

'백준 온라인 저지 - 단계별로 풀어보기 > 그래프' 카테고리의 다른 글

1753. 최단경로  (0) 2022.07.02

0. 개요

https://www.acmicpc.net/problem/11729

 

11729번: 하노이 탑 이동 순서

세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승들이 다음 규칙에 따라 첫 번째 장대에서 세 번째 장대로

www.acmicpc.net

재귀 문제 중에서 가장 이해가 안되었던 문제였습니다.

코드를 봐도 이해가 안갔던 문제인데 오늘 접근방법이 떠올랐고 문제를 드디어 해결했습니다.

저와 같이 코드를 봤어도 이해가 안갔던 분들을 위하여 상세하게 풀이 과정을 서술해보겠습니다.

 

 

1. 풀이과정

문제에서 제시하는 바는 위와 같습니다.

1번 기둥에 있는 원판을 모두 3번으로 이동 시키는 것입니다.

하노이의 탑 문제의 규칙은 모두 알고 있다고 가정을 하겠습니다.

 

 

1-1. 이동할 방향 선정?

(아래는 도출 과정입니다. 답을 원하시는 분은 1-2로 바로 넘어가 주세요.)

제가 맨처음 접근한 방식은 다음과 같습니다.

맨위의 녀석을 2번과 3번 둘중 하나에 선택하여 이동하고 그다음 원판을 같이 이동, 이동,.. 하여 맨 밑에까지,

맨위의 작은 원판부터 접근을 했습니다.

 

그러자 하나의 문제점에 도달했습니다.

원판의 갯수에 따라 제일 작은 원판이 2번에 가야 할 수도, 3번에 가야 할 수도 있는 상황이 되었습니다.

조건이 많아지는 것은 매우 귀찮아지는 일입니다.

또한 알고리즘은 똑같은 동작 원리에 의하여 이동하는 것이 일반적으로 편하겠죠.

 

그렇다면 어떻게 해야할까요?

여기서 떠올린 점이 다음과 같습니다.

 

1-2. 맨 아래서 부터 선택하여 올라가자.

가장 큰 원판을 3번으로 이동시켜 놓는다면 모든 일이 해결됩니다.

 

가장 큰 선반을 3번으로 옮긴다고 가정했을 때, 위의 나머지 4개를 옆으로 치워줘야 3번으로 이동 할 수 있겠죠.

 

그렇다면 파란색 구간을 옮기기 위해서는 파란색 구간의 가장 넓은 원판을 치워야합니다.

 

이제 감이 좀 오시는가요?

 

파란색 구간을 치우기 위해서는 녹색 구간을 치워야하고..

녹색 구간을 치우기 위해서는 노란색 구간을 치워야하고..

 

여기서 재귀적 특징이 나타납니다.

 

그럼 치우는 것 까지는 알겠습니다.

 

그렇다면 어디로 치워줘야 할까요?

위의 그림을 살펴보면 알 수 있습니다.

편의상 가장 큰 원판을 5부터 가장 작은 원판을 1이라 하겠습니다.

 

먼저 우리의 목표는 1번에 있는 5번 원판을 3번으로 이동시키는 것입니다.(빨간색 화살표)

그렇게 하기 위해서는 1~4번까지의 윗부분이 자리를 비켜줘야 합니다.

 

파란색 부분은 2번밖에 비켜줄 자리가 없는 것을 확인할 수 있습니다.

 

다만 4개의 원판을 한번에 움직이는 것은 불가능하죠.

재귀의 원리에 의해서 맨 꼭대기 부터 이동해줘야 합니다.

다만 빨간색이 3번으로 가야하기 때문에 빨간색을 3번으로 먼저 보내고,

4-3-2-1 순서로 2번과 3번을 교차해서 보내주면 위와 같이 됩니다.

 

즉, 우리가 처음 할 일은 먼저 각 원판이 어디로 자리를 비켜줄지를 먼저 정하는겁니다.

 

 

먼저 위와 같이 1번에 있는 4개의 원판이 자리를 비켜줬다고 생각해봅시다.

그러면 가장큰 5번 원반을 3번으로 이동하기만 하면 되겠죠.

 

 

그 다음 상황은 다음과 같습니다.

5번 원판은 3번으로 이동했습니다.

그럼 나머지 원판을 3번으로 이동시켜 줘야겠죠.

 

이것 또한 위와 같이 각각 이동할 수 있는 위치를 살펴보고 빈 자리 에다 이동해줍니다.

 

여기서 빈자리는 어떻게 찾을것인가에 대한 논의가 필요합니다!!

 

1번 2번 3번 세개의 위가 있습니다.

 

1+2+3을 한다면 6이라는 값이 나오겠죠?

 

만약 지금 2 -> 3으로 갔다고 생각한다면, 1번이 비어있습니다.

 

이는 다음과 같은 식으로 구할 수 있습니다.

 

6 - (2 + 3) = 1

 

즉, 6에서 현재 위치와 도착 지점을 더한 값을 뺀다면 다음에는 어디로 보내야 할지 알 수 있습니다.

 

위의 예제로 생각한다면,

주황색은 3번으로 갔습니다. 2->3입니다.

 

그렇다면 다음에 가야할 위치는 6-(2+3) = 1 번째 라고 알 수 있습니다.

 

나머지 부분에 대해서도  동일하게 적용하면 됩니다.

 

 

그렇다면 아래와 같이 이동하게 되고 끝나겠죠.

 

2. 소스코드

 

#include<iostream>

using namespace std;

int cnt = 0;

/* ans() 함수는 hanoi() 함수에서 출력만 제거한 함수입니다. */
/* 문제에는 총 이동 횟수를 먼저 출력한 뒤 이동한 위치를 출력하도록 되어 있어 이동 횟수를 먼저 구하는 함수입니다 */
/* 내부적인 설명은 아래 hanoi() 함수에 적도록 하겠습니다. */

void ans(int s, int e, int n) {
	if (n == 1) {
		cnt++;
		return;
	}
	int next = 6 - (s + e);
	ans(s, next, n - 1);
	cnt++;
	ans(next, e, n - 1);
}

/* 시작과 끝, 현재 몇번째 원판을 빼야하는지에 대한 값을 매개변수로 받아옵니다. */
void hanoi(int s, int e, int n) {
	if (n == 1) {
        /* 만약 빼야하는 원판이 1, 즉 제일 작을 때는 다른걸 굳이 안옮기고 바로 옮길 수 있습니다 */
		cout << s << " " << e<<"\n";
		return;
	}
    
    /* 6에서 시작+끝 값을 빼줌으로써 다음 위치를 구합니다. */
	int next = 6 - (s + e);
    
    /* 현재의 가장 큰 원판을 옮기기 위하여 그 위의 것들을 이동시켜 줍니다. */
	hanoi(s, next, n - 1);
    
    /* 가장 큰 원판을 이동 시킵니다 */
	cout << s << " " << e<<"\n";
    
    /* 가장 큰 원판을 이동시키기 위하여 비켜준 친구들을 다시 가장큰 원판 위로 옮깁니다 */
	hanoi(next, e, n - 1);
}

int main(void) {
	int N;

	cin >> N;
	
    /* ans를 통하여 cnt 값을 구하고 출력 후 hanoi() 함수를 실행시켜 이동 경로를 출력합니다. */
	ans(1, 3, N);
	cout << cnt << "\n";
	hanoi(1, 3, N);
}

 

제가 가장 어려웠던 것은

hanoi(s, next, n-1);

cout<<s<<" '<<e"\n";

hanoi(next, e, n-1); //이부분

이었습니다.

 

이부분은 아래와 같이 비켜준 원판들을 가장 큰 원판이 이동한 위치로 옮겨준다고 생각하시면 됩니다.

위가 이부분에 해당하는 코드이고, 

hanoi(s, next, n-1);

cout<<s<<" '<<e"\n";

 

위가 이부분에 해당하는 코드라고 보시면 됩니다.

hanoi(next, e, n-1); 

'백준 온라인 저지 - 단계별로 풀어보기 > 재귀' 카테고리의 다른 글

2447 - 별 찍기 - 10  (0) 2020.12.30
10870 - 피보나치 수 5  (0) 2020.12.29

+ Recent posts