문제 링크


www.acmicpc.net/problem/17406

 

17406번: 배열 돌리기 4

크기가 N×M 크기인 배열 A가 있을때, 배열 A의 값은 각 행에 있는 모든 수의 합 중 최솟값을 의미한다. 배열 A가 아래와 같은 경우 1행의 합은 6, 2행의 합은 4, 3행의 합은 15이다. 따라서, 배열 A의

www.acmicpc.net

 

접근 방법


 처음에 문제를 해결하고 나서 왜맞틀을 시전했던 문제입니다. 문제를 잘 못 이해해서 생긴 문제인데 문제에서 배열의 회전 명령어를 순차적으로 실행하는 것이 아닌 주어진 명령어의 모든 조합을 다 실행하여 그 중 최소를 구하는 문제였습니다. 

 

문제 풀이의 순서는 다음과 같습니다.

1. 가능한 명령의 조합을 구한다.

2. 한 조합에서 한 명령어를 실행한다.

3. 한 명령어를 실행한다.

4. 한 명령어에 s~1까지 순차적으로 배열을 회전 시킨다.

5. 한 조합의 명령어를 모두 실행시킨 후 배열의 값을 구한다.

6. 다음 조합도 위의 과정을 실행하며 최솟값을 갱신한다.

 

소스 코드


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

using namespace std;

typedef struct info {
	int r; int c; int s;
};

vector <vector<int>> map;
vector <info> inst;
int N, M, K;

void init() {
	
	cin >> N >> M >> K;

	for (int i = 0; i < N; i++) {
		vector<int> v;
		for (int j = 0; j < M; j++) {
			int num; cin >> num;
			v.push_back(num);
		}
		map.push_back(v);
	}
	for (int i = 0; i < K; i++) {
		int r, c, s;
		cin >> r >> c >> s;
		inst.push_back({ r - 1, c - 1, s });
	}
}

vector<vector<int>> sequence(vector<int> &seq, vector<vector<int>> &seqs, vector<bool> &visited) {

	if (seq.size() == inst.size()) {
		seqs.push_back(seq);
		return seqs;
	}
	for (int i = 0; i < inst.size(); i++) {
		if (!visited[i]) {
			visited[i] = true;
			seq.push_back(i);
			sequence(seq, seqs, visited);
			visited[i] = false;
			seq.pop_back();
		}
	}
	return seqs;
}

int findMin(vector<vector<int>> newmap) {
	
	int minimum = 0xFFFFFFF;

	for (int i = 0; i < N; i++) {
		int sum = 0;
		for (int j = 0; j < M; j++)
			sum += newmap[i][j];
		minimum = min(sum, minimum);
	}
	return minimum;
}

void rotate(int x, int y, int dist, vector<vector<int>> &ret) {

	vector<vector<int>> tmp = ret;

	for (int i = y - dist; i < y + dist; i++) 
		ret[x - dist][i + 1] = tmp[x - dist][i]; //높이 고정
	for (int i = x - dist; i < x + dist; i++)
		ret[i + 1][y + dist] = tmp[i][y + dist];  // 축 고정
	for (int i = y + dist; i > y - dist; i--)
		ret[x + dist][i - 1] = tmp[x + dist][i]; //높이 고정
	for (int i = x + dist; i > x - dist; i--)
		ret[i - 1][y - dist] = tmp[i][y - dist];  // 축 고정
}

int processing(vector<int> seq) { // 한 명령어 집합에 대한 것
	
	vector<vector<int>> newmap = map;

	for (int i = 0; i < seq.size(); i++) {

		int r = inst[seq[i]].r;
		int c = inst[seq[i]].c;
		int s = inst[seq[i]].s;

		for (int j = 1; j <= s; j++)
			rotate(r, c, j, newmap);
	}
	return findMin(newmap);
}

int solve() {
	
	vector <vector<int>> seqs;
	vector <int> seq;
	vector <bool> visited(6, false);
	int minimum = 0xFFFFFFF;

	seqs = sequence(seq, seqs, visited);

	for (int i = 0; i < seqs.size();i++) 
		minimum = min(minimum, processing(seqs[i]));
	
	return minimum;
}

int main() {
	
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	init();

	cout << solve() << "\n";
}

개발 환경 : VS2017

질문, 지적 환영합니다!

 

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 17472 다리만들기2  (0) 2021.01.04
[C++] 백준 17281 야구  (0) 2021.01.04
[C++] 백준 3190 뱀  (0) 2021.01.03
[C++] 백준 17136 색종이 붙이기  (0) 2021.01.02
[C++] 백준 9470 Strahler 순서  (0) 2020.09.25

문제 링크


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

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크�

www.acmicpc.net

접근 방법


 위 문제는 벽 3개를 세울 수 있는 모든 경우에 대해 벽을 세운 후 바이러스를 퍼뜨려 안전영역의 최댓값을 구하면 된다. 

 

 벽을 3개를 세우는 방법은 백트래킹으로 가능하나 for문을 3중으로 사용하는 것도 가능하다. 필자는 3중 for문을 사용했으며, 취향에 따라 방법을 달리하여도 된다. 벽을 3개를 세운 뒤 바이러스를 퍼뜨리는 방법은 BFS를 사용하면 된다.

 

1. 일단 main()에서 입력을 받는 동시에 빈 공간의 위치와 바이러스의 위치의 정보를 저장해 두었다.

for (int i = 0; i < n; i++) 
	for (int j = 0; j < m; j++) {
    cin >> room[i][j];
    	if (room[i][j] == 0)
    		zeros.push_back({ i,j }); //빈공간
    	else if (room[i][j] == 2)
    		virus.push_back({ i,j }); //바이러스 초기위치
    }

2. 3중 for문을 사용하여 벽 3개를 세울 위치를 각각 first, second, third로 받았으며 해당 위치를 map에서 1로 변경 후 bfs를 사용하여 바이러스를 퍼뜨려 바이러스가 퍼진 공간을 cnt로 반환받았으며 안전영역의 수는 아래와 같이 구할 수 있다.

int safe = zeros.size() - 3 - cnt;

safe(바이러스가 퍼진 후 안전영역) =

zeros.size()(바이러스가 퍼지기 이전의 안전영역 수) - 3(벽을 새로 세운 공간) - cnt(바이러스가 퍼진 영역)

 

 

 

소스 코드


#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <string.h>

using namespace std;

int room[8][8];
vector <pair<int, int>> zeros;
vector <pair<int, int>> virus;
pair<int, int> direction[4] = { {1,0}, {-1,0}, {0,1}, {0,-1} };
int n, m;
int max_ = 0;

int bfs(int x, int y) {

	int cnt = 0;
	queue < pair<int, int>> q;
	q.push({ x,y });
	while (!q.empty()) {
		auto elem = q.front();
		int i = elem.first;
		int j = elem.second;
		q.pop();
		for (int k = 0; k < 4; k++) {
			int next_x = i + direction[k].first;
			int next_y = j + direction[k].second;
			if (0 <= next_x && next_x < n && 0 <= next_y && next_y < m && room[next_x][next_y] == 0) {
				q.push({ next_x, next_y });
				room[next_x][next_y] = 2;
				cnt++;
			}
		}
	}
	return cnt;
}

void build_wall(int n, int m) {

	for (int i = 0; i < zeros.size(); i++) {
		for (int j = i + 1; j < zeros.size(); j++) {
			for (int k = j + 1; k < zeros.size(); k++) {
				int cnt = 0;
				auto first = zeros[i];
				auto second = zeros[j];
				auto third = zeros[k];
				room[first.first][first.second] = 1;
				room[second.first][second.second] = 1;
				room[third.first][third.second] = 1;
				for (auto &elem : virus) {
					cnt += bfs(elem.first, elem.second); //바이러스가 퍼진 영역의 수
				}
				int safe = zeros.size() - 3 - cnt;
				max_ = max(max_, safe);
				for (auto &elem : zeros) {
					room[elem.first][elem.second] = 0;
				}
				room[first.first][first.second] = 0;
				room[second.first][second.second] = 0;
				room[third.first][third.second] = 0;
			}
		}
	}
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	memset(room, -1, sizeof(room));

	cin >> n >> m;
	for (int i = 0; i < n; i++) 
		for (int j = 0; j < m; j++) {
			cin >> room[i][j];
			if (room[i][j] == 0)
				zeros.push_back({ i,j }); //빈공간
			else if (room[i][j] == 2)
				virus.push_back({ i,j }); //바이러스 초기위치
		}
	build_wall(n, m);
	cout << max_ << "\n";
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 2580 스도쿠  (0) 2020.06.10
[C++] 백준 4195 친구네트워크  (0) 2020.06.09
[C++] 백준 17779 게리맨더링2  (0) 2020.05.29
[C++] 백준 17837 새로운 게임 2  (0) 2020.05.29
[C++] 백준 1520 내리막길  (0) 2020.05.26

문제 링크


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

 

1520번: 내리막 길

여행을 떠난 세준이는 지도를 하나 구하였다. 이 지도는 아래 그림과 같이 직사각형 모양이며 여러 칸으로 나뉘어져 있다. 한 칸은 한 지점을 나타내는데 각 칸에는 그 지점의 높이가 쓰여 있으�

www.acmicpc.net

 

접근 방법


 

 이 문제는 내리막 길(현재보다 더 값이 작은 지점)으로만 따라 갔을 때, 목적지에 도착하는 길의 수를 구하는 문제이며, 문제를 보았을때 DFS를 사용하면 top-down방식으로 가능할 것이고, BFS를 사용하면 bottom-up방식이 가능하다. 필자는 DFS를 이용한 top-down방식으로 해결하였다.

 

(1) 변수

 dp[i][j]는 i,j에서 출발하였을 때, 내리막 길을 따라 도착지에 도착하였을 경우에 대한 길의 수이며, 방문 여부로도 사용된다. 초기값을 0으로 사용하게 되면, 방문을 하였어도 도착지점까지 못 간다면 계속 0이기 때문에 방문여부를 확인하기 어렵기 때문에 초기값을 -1로 하였다.

 

(2) solve()

int &ret = dp[x][y];
if (ret != -1) return ret;
if (x == n && y == m) return ret = 1;
ret = 0;

 함수가 호출되었을때, 가장 먼저 방문여부( if (ret != -1) )를 판단한다. 중복되는 연산을 피하기 위해서이다. 방문을 하였으면 해당 dp에 저장된 값을 return한다. 좀 더 자세히 설명을 하자면 어차피 현재 함수의 x,y에서 갈 수 있는 경로를 이미 다 탐색을 한 값이 dp[x][y]에 저장되어 있으므로 함수를 더 진행시킬 필요가 없다는 것이다.   

 그 다음 도착지점에 도착했다면( if (x == n && y == m) ), 도착한 경로에 대한 경우가 추가 되었으므로 1을 return한다.

 위의 두가지 경우가 아니면 방문을 했다는 의미로 값을 0으로 바꿔준다.

 

for (int i = 0; i < 4; i++) {
		int next_x = x + direction[i].first;
		int next_y = y + direction[i].second;
		if (1 <= next_x && next_x <= n && 1 <= next_y && next_y <= m) 
			if (map[next_x][next_y] < map[x][y]) 
				ret += solve(next_x, next_y);
}

그 이후 현재좌표의 상하좌우 4방향에 대해 map에 포함되어 있는 범위 내에서, 값이 더 작은 곳으로 이동시키면 된다.

소스 코드


#include <iostream>
#include <string.h>

using namespace std;
int n, m;
int map[501][501];
int dp[501][501];
pair<int, int> direction[4] = { {1,0}, {0,1}, {-1,0}, {0,-1} };

int solve(int x, int y) {
    
	int &ret = dp[x][y];
	if (ret != -1) return ret;
	if (x == n && y == m) return ret = 1;
	ret = 0;
    
	for (int i = 0; i < 4; i++) {
		int next_x = x + direction[i].first;
		int next_y = y + direction[i].second;
		if (1 <= next_x && next_x <= n && 1 <= next_y && next_y <= m) 
			if (map[next_x][next_y] < map[x][y]) 
				ret += solve(next_x, next_y);
	}
	return ret;
}

int main() {
    
	ios_base::sync_with_stdio(NULL);
	cin.tie(0);
	cout.tie(0);
    
	memset(dp, -1, sizeof(dp));
	cin >> n >> m;
    
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) 
			cin >> map[i][j];
	cout << solve(1, 1);
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 17779 게리맨더링2  (0) 2020.05.29
[C++] 백준 17837 새로운 게임 2  (0) 2020.05.29
[C++] 백준 5373 큐빙  (0) 2020.05.26
[C++] 백준 13460 구슬 탈출 2  (0) 2020.05.26
[C++] 백준 15684 사다리 조작  (0) 2020.05.25

문제 링크


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

 

15684번: 사다리 조작

사다리 게임은 N개의 세로선과 M개의 가로선으로 이루어져 있다. 인접한 세로선 사이에는 가로선을 놓을 수 있는데, 각각의 세로선마다 가로선을 놓을 수 있는 위치의 개수는 H이고, 모든 세로선

www.acmicpc.net

 

접근 방법


 이 문제는 사다리에 추가적으로 가로선을 0개 부터 최대 3개까지 놓는 가능한 모든 경우의 수를 다 보아야 해결 할 수 있다.  결국 조합 문제로 해석될 수 있으므로, 백트래킹을 하며 모든 조합에 대해 문제의 조건은 1번째부터 i번째에서 출발할 때 순서대로 1번째부터 i번째의 도착 지점에 도달하면 카운트하고, 그 카운트가 4이상이거나 가능한 방법이 없다면 -1을 출력하면 된다.

 

(1) 변수

map[h][l]는 h의 높이, l번째 라인이 h의 높이, l+1번째 라인에 가로선이 연결되어 있다면 1, 아니면 0으로 나타낸다.  

 

(2) go() 함수

bool go() {
	for (int i = 1; i <= N; i++) {
		int nowl = i; 
		for (int nowh = 0; nowh <= H; nowh++){
			if (map[nowh][nowl] == 1) 
				nowl++;
			else if (map[nowh][nowl - 1] == 1) 
				nowl--;
		}
		if (nowl != i)
			return false;
	}
	return true;
}

이 함수는 사다리의 출발지점과 도착지점이 같은지 따라가보는 함수이다.

 모든 사다리의 출발지점 1~N에 대해 확인 하여야 하기 때문에 for문에서 경우를 검사해보자. 현재 출발 라인을 nowl = i 라 하고, nowh(0~H)를 따라갈때, 우측에 사다리가 있는 경우 (if(map[nowh][nowl] == 1))   라인을 오른쪽으로 옮겨갈 것(nowl++)이고, 좌측에 사다리가 있는 경우 (if(map[nowh][nowl - 1] == 1))   라인을 왼쪽으로 옮겨갈 것(nowl--)이다.

 

(3) solve()함수

void solve(int cnt, int now_h, int now_l) {
	if (go()) {
		result = min(cnt, result);
		return;
	}
	if (cnt == 3) 
		return;

	for (int h = now_h; h <= H; h++) {
		for (int l = now_l; l < N; l++) {
			if (map[h][l] == 0 && map[h][l - 1] == 0 && map[h][l + 1] == 0) {
				map[h][l] = 1;
				solve(cnt + 1, h, l);
				map[h][l] = 0;
			}
		}
		now_l = 1;
	}
}

이 함수는 조합을 보기 위한 백트래킹(DFS) 함수이다. 중복되는 경우를 보지 않기 위해 매게변수로(nowl, nowh)를 사용하였으며, 위 함수가 호출되는 모든 경우에 대해 위에 설명한 go()함수로 조건을 만족하는지 검사한다. cnt가 3보다 커지는 경우는 고려하지 않으므로 cnt는 3에서 return을 하게 된다.

 

 가로선을 추가 할 수 있는 경우인,

양쪽에 가로라인이 존재하지 않을 때(if (map[h][l] == 0 && map[h][l - 1] == 0 && map[h][l + 1] == 0))

가로라인을 추가(map[h][l] = 1)하고, 함수를 진행 시킨뒤(solve(cnt + 1, h, 1)), 함수가 종료되면 해당 가로라인을 다시 삭제 (map[h][l] = 0)하면 된다. 

 

소스 코드


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

using namespace std;

int N, M, H;
int map[32][12]; //height, line
int result = 4;	

bool go() {
	
	for (int i = 1; i <= N; i++) {
		int nowl = i; 
		for (int nowh = 0; nowh <= H; nowh++){
			if (map[nowh][nowl] == 1) 
				nowl++;
			else if (map[nowh][nowl - 1] == 1) 
				nowl--;
		}
		if (nowl != i)
			return false;
	}
	return true;
}

void solve(int cnt, int now_h, int now_l) {
	if (go()) {
		result = min(cnt, result);
		return;
	}
	if (cnt == 3) 
		return;

	for (int h = now_h; h <= H; h++) {
		for (int l = now_l; l < N; l++) {
			if (map[h][l] == 0 && map[h][l - 1] == 0 && map[h][l + 1] == 0) {
				map[h][l] = 1;
				solve(cnt + 1, h, l);
				map[h][l] = 0;
			}
		}
		now_l = 1;
	}
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> N >> M >> H;
	for (int i = 1; i <= M; i++) {
		int a, b;
		cin >> a >> b; //a높이 b라인
		map[a][b] = 1;
	}
	solve(0, 1, 1);
	
	if (result >= 4)
		cout << -1 << "\n";
	else
		cout << result << "\n";
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 5373 큐빙  (0) 2020.05.26
[C++] 백준 13460 구슬 탈출 2  (0) 2020.05.26
[C++] 백준 14891 톱니바퀴  (0) 2020.05.25
[C++} 백준 14890 경사로  (0) 2020.05.25
[C++] 백준 15683 감시  (0) 2020.05.20

문제 링크


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

 

14500번: 테트로미노

폴리오미노란 크기가 1×1인 정사각형을 여러 개 이어서 붙인 도형이며, 다음과 같은 조건을 만족해야 한다. 정사각형은 서로 겹치면 안 된다. 도형은 모두 연결되어 있어야 한다. 정사각형의 변�

www.acmicpc.net

접근 방법


 시작지점부터 상하좌우로 인접한 지점을 따라가며 백트래킹을 하여 테트로미노를 이루는 4칸이 완성이 되었을 때 최댓값을 갱신해주며 풀면 된다. 하지만 주의해야될 점이있다. 

 

 

 이런 경우에 3과 4는 인접해 있지 않지만 위와 같은 경우도 문제에서 요구하는 경우의 수가 되기 때문이다.

이 점을 유의하여 문제를 풀어보자.

if (cnt == 3) {
	if (stk[0].x == stk[1].x && stk[1].x == stk[2].x) {
		info fuck[2] = { {1, 0} ,{-1, 0} };
		for (int i = 0; i < 2; i++) {
			int next_x = stk[1].x + fuck[i].x;
			int next_y = stk[1].y + fuck[i].y;
			if (0 <= next_x && next_x < N && 0 <= next_y && next_y < M && !visited[next_x][next_y]) {
				visited[next_x][next_y] = true;
				stk.push_back({ next_x, next_y });
				solve(cnt + 1, next_x, next_y);
				stk.pop_back();
				visited[next_x][next_y] = false;
			}
		}
	}
}

 일단 그림에서 설명한 것과 동일하게 3개의 폴리오미노가 수평으로 나란할 때, 즉 코드상으로 x의 값이 동일 할때에 대한 처리 부분이다. 수직으로 나란한 경우도 크게 다른점이 없으니 수평인 경우만 설명하겠다.

 

 일단 3개의 폴리오미노가 이루어졌을 때 -> if(cnt == 3) ) 지금까지 이룬 폴리오미노의

집합 stk[i] (0 <= i < 3) 의 x값이 모두 동일 하다면 -> if (stk[0].x == stk[1].x && stk[1].x == stk[2].x)

3개의 폴리오미노중 가운대인 stk[1]에 위 아래 방향으로 더하여(int next_x = stk[1].x + fuck[i].x,  
int next_y = stk[1].y + fuck[i].y) 다음 스탭으로 진행을 시키면 된다. 

  

소스 코드


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

using namespace std;

typedef struct info {
	int x;
	int y;
};

vector <info> stk;
info next_dir[4] = { {1,0}, {-1,0}, {0,-1}, {0,1} };
int map[500][500];
bool visited[500][500];
int N, M;
int result = -1;

void solve(int cnt, int x, int y) {
	if (cnt == 4) {
		int sum = 0;
		for (int i = 0; i < 4; i++) {
			sum += map[stk[i].x][stk[i].y];
			result = max(result, sum);
		}
		return;
	}
	if (cnt == 3) {
		if (stk[0].x == stk[1].x && stk[1].x == stk[2].x) {
			info fuck[2] = { {1, 0} ,{-1, 0} };
			for (int i = 0; i < 2; i++) {
				int next_x = stk[1].x + fuck[i].x;
				int next_y = stk[1].y + fuck[i].y;
				if (0 <= next_x && next_x < N && 0 <= next_y && next_y < M && !visited[next_x][next_y]) {
					visited[next_x][next_y] = true;
					stk.push_back({ next_x, next_y });
					solve(cnt + 1, next_x, next_y);
					stk.pop_back();
					visited[next_x][next_y] = false;
				}
			}
		}
		else if (stk[0].y == stk[1].y && stk[1].y == stk[2].y){
			info fuck[2] = { {0, 1} ,{0, -1} };
			for (int i = 0; i < 2; i++) {
				int next_x = stk[1].x + fuck[i].x;
				int next_y = stk[1].y + fuck[i].y;
				if (0 <= next_x && next_x < N && 0 <= next_y && next_y < M && !visited[next_x][next_y]) {
					visited[next_x][next_y] = true;
					stk.push_back({ next_x, next_y });
					solve(cnt + 1, next_x, next_y);
					stk.pop_back();
					visited[next_x][next_y] = false;
				}
			}
		}
	}
	for (int i = 0; i < 4; i++) {
		int next_x = x + next_dir[i].x;
		int next_y = y + next_dir[i].y;
		if (0 <= next_x && next_x < N && 0 <= next_y && next_y < M && !visited[next_x][next_y]) {
			visited[next_x][next_y] = true;
			stk.push_back({ next_x, next_y });
			solve(cnt + 1, next_x, next_y);
			stk.pop_back();
			visited[next_x][next_y] = false;
		}
	}

}

int main() {

	ios_base::sync_with_stdio(NULL);
	cin.tie(0);
	cout.tie(0);
	cin >> N >> M;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			cin >> map[i][j];
		}
	}
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			visited[i][j] = true;
			stk.push_back({ i,j });
			solve(1, i, j);
			stk.pop_back();
			visited[i][j] = false;
		}
	}
	cout << result << "\n";
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++} 백준 14890 경사로  (0) 2020.05.25
[C++] 백준 15683 감시  (0) 2020.05.20
[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19
[C++] 백준 1697 숨바꼭질  (0) 2020.05.19
[C++] 백준 14501 퇴사  (0) 2020.05.19

문제 링크


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

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로��

www.acmicpc.net

 

접근 방법


 문제를 읽어보면 BFS알고리즘으로 풀어야된다는 직감은 확실히 들것이다. 하지만 벽을 부수는 것이 가능한 기회가 단 한번 주어지는대, 이로 인해 풀이에 있어 접근이 쉽지 않을 것이다.

 일단 문제를 보자마자 떠올릴 수 있는 방법이 있는데 map에서 모든 벽들을 한번씩 0으로 바꿔 보고 BFS를 해보는 것이다. 하지만 이렇게 하는 것은 굉장히 비효율적이고 시간안에 해결 할 수 없다. 

 문제 풀이에 있어 핵심은 어떤 특정한 지점에 "벽을 한번 부수고 난 이후에 도착 한 시간" 과 "벽을 한번도 부수지 않은 상태에서 도착한 시간"이 다르다는 것이다. 이것을 이해했다면 문제를 풀어보자.

 

(1) 변수

   isbreak는 벽을 부순적이 없다면 false 부순적이 있다면 true이다.

그렇다면 visited[i][j][ isbreak=true ]i, j위치에 벽을 부수고 나서 도착했을때의 시간이고,

visited[i][j][ isbreak=false ]벽을 부수지 않고 도착했을때의 시간이 된다. 

 

(2) 풀이

  전반적인 부분이 다른 BFS문제들과 매우 비슷하므로, 가장 핵심이 되는 부분만 설명하겠다.

if (!isbreak && map[next_x][next_y] == 1) {
	//벽을 부순적이 없는데 가로막힌 길이라면
    visited[next_x][next_y][true] = visited[x][y][false] + 1;
    q.push({ next_x, next_y, true });
}
else if (map[next_x][next_y] == 0 && visited[next_x][next_y][isbreak] == 0) { 
    //지나갈 수 있는 길이고, 방문한적이 없다면
    visited[next_x][next_y][isbreak] = visited[x][y][isbreak] + 1;
    q.push({ next_x, next_y, isbreak });
}

  첫 번째 if문은 isbreak = false이고 map[next_x][next_y] = 1 인 조건에서 진입이 가능한데, 이를 쉽게 풀어쓰면

"next_x, next_y의 좌표에 도달할 때 까지 벽을 부순적이 없는데 벽이 있다면" 진입을 한다는 것이다.

진입을 했다면 이제 벽을 부수고 해당지점에 도착했다는 것을 나타내주면 된다.

  두 번째 if문은 "일단 지나갈 수 있는 길이고, 방문한 적이 없다면" 방문을 하여 시간을 기록하고 큐에 push하면 된다. 이는 너무나도 당연한 것이기에 자세한 설명은 하지 않겠다.

 

  bfs( )함수의 while문 안에서 좌표가 n,m이면 도달한 시간을 return하게 된다. 그 말은 즉 while()안에서 return을 하지 못한다면 map[n][m]에 도달하지 못했다는 의미가 되므로 -1을 return한다. 

소스 코드


#include <iostream>
#include <queue>

using namespace std;

typedef struct info {
	int x;
	int y;
	bool isbreak;
};

int map[1001][1001];
int visited[1001][1001][2]; //같은 지점이여도 벽을 부수고 도착했을때와 벽을 부수지 않고 도착했을 때 값이 달라지기 때문
int n, m;

int bfs() {

	queue <info> q;
	visited[1][1][false] = 1;
	q.push({ 1,1,false }); //시작 지점 , 벽을 아직 부수지 않았음

	while (!q.empty()) {

		int x = q.front().x;
		int y = q.front().y;
		bool isbreak = q.front().isbreak;
		q.pop();

		if (x == n && y == m) //목표지점에 도착
			return visited[x][y][isbreak];

		pair<int, int> direction[4] = { {1,0}, {-1,0} ,{0,1} ,{0,-1} };

		for (int i = 0; i < 4; i++) {

			int next_x = x + direction[i].first;
			int next_y = y + direction[i].second;

			if (!(next_x < 1 || n < next_x || next_y < 1 || m < next_y)) { //다음 좌표가 map에 포함되어 있다면
				if (!isbreak && map[next_x][next_y] == 1) {
					//벽을 부순적이 없는데 가로막힌 길이라면
					visited[next_x][next_y][true] = visited[x][y][false] + 1;
					q.push({ next_x, next_y, true });
				}
				else if (map[next_x][next_y] == 0 && visited[next_x][next_y][isbreak] == 0) { 
					//지나갈 수 있는 길이고, 방문한적이 없다면
					visited[next_x][next_y][isbreak] = visited[x][y][isbreak] + 1;
					q.push({ next_x, next_y, isbreak });
				}
			}
		}
	}
	return -1;
}

int main() {

	cin >> n >> m;

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			scanf("%1d", &map[i][j]);

	cout << bfs();
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 15683 감시  (0) 2020.05.20
[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 1697 숨바꼭질  (0) 2020.05.19
[C++] 백준 14501 퇴사  (0) 2020.05.19
[C++] 백준 12865 평범한 배낭  (2) 2020.05.18

문제 링크


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

 

1697번: 숨바꼭질

문제 수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가

www.acmicpc.net

 

접근 방법


어떤 지점에 도달하는데 있어 최초로 도달한 시간을 기록하는 문제이기에 BFS를 사용해야겠다고 떠올렸다.

 

(1) 변수

 visited[i]는 i에 도달하였을때의 시간을 기록해두며, 방문 여부도 동시에 판단할 수 있게 한다.

next[i]는 다음으로 이동할 지점이다.     

 

(2) 풀이

  다음 이동할 지점을 기록한 next[i] 가 도착지점(to)에 최초 도달할 때(visited[next[i]]가 -1인 상태) visited[now] + 1을

반환하면 된다.                                                                               

소스 코드


 

#include <iostream>
#include <queue>
#include <string.h>

using namespace std;

int visited[100001];

int bfs(int from, int to) {
	queue <int> q;
	q.push(from);
	visited[from] = 0;

	while (!q.empty()) {
		int now = q.front();
		int next[3] = { now + 1, now - 1, now * 2 };
		q.pop();
		for (int i = 0; i < 3; i++) 			
			if (0 <= next[i] && next[i] <= 100000 && visited[next[i]] == -1) {
				if (next[i] == to) return visited[now] + 1;
				q.push(next[i]);
				visited[next[i]] = visited[now] + 1;
			}
	}
	return 0;
}

int main() {
	int from, to;
    memset(visited, -1, sizeof(visited));
	cin >> from >> to;
	cout << bfs(from, to);
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19
[C++] 백준 14501 퇴사  (0) 2020.05.19
[C++] 백준 12865 평범한 배낭  (2) 2020.05.18
[C++] 백준 15686 치킨 배달  (0) 2020.05.18

문제 링크


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

 

14501번: 퇴사

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

www.acmicpc.net

 

접근 방법


동적계획법을 사용하여 풀이하였으며, 재귀함수를 이용한 top-down방식을 사용하였으며 크게 어렵지 않은 문제였다.

 

(1) 변수

   dp[i]는 i번째 날부터 상담을 하기 시작하였을 때 가질 수 있는 최대의 비용이다.

 

(2) solve( )

   함수 내에서 cur은 현재 날짜를 의미한다.

if (cur > n + 1) 
	return -1;
if (cur == n + 1) 
	return dp[cur] = v[cur].second; //딱 마지막 날이면 그값을 반환

 첫번째 if문은 현재날짜가 퇴사일을 지나간 경우이고, 두번째 if문은 정확히 퇴사 날짜를 포함하여 현재 상담이 종료되는 경우이다. 이 두가지 조건이 아니라면 다음 상담을 잡을 수 있는 가능성이 존재한다는 것이다. 

for (int i = cur + v[cur].first; i <= n + 1; i++) { //미팅이 시작 가능한 경우의 한에서
	int ret = solve(i);
	if (ret != -1) 
		dp[cur] = max(ret + v[cur].second, dp[cur]);
}
return dp[cur];

 

 i는 현재 날짜로 부터 상담을 마친 이후 가능한 모든 다음 상담의 시작 날짜이다. 하지만 위 for문에서 (i > n+1), 즉 상담날짜의 시작시점이 퇴사일이 지난 이후라면 자연스럽게 무시가 될것이다.  max( )에서 ret + v[cur].second는 현재의 상담에 대한 비용을 계속해서 더해나간 것이고, 메모되었던 dp[cur]와 비교를 하여 더 큰 값으로 갱신 할 것이다. 

 

소스 코드

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

using namespace std;
vector<pair <int, int>> v(20, {0,0});
int dp[17];
int n;

int solve(int cur) { //1
	if (cur > n + 1) 
		return -1;
	if (cur == n + 1) 
		return dp[cur] = v[cur].second; //딱 마지막 날이면 그값을 반환
	
	else { //만약 next로 진입이 가능하다면 
		for (int i = cur + v[cur].first; i <= n + 1; i++) { //미팅이 시작 가능한 경우의 한에서
			int ret = solve(i);
			if (ret != -1) 
				dp[cur] = max(ret + v[cur].second, dp[cur]);
		}
		return dp[cur];
	}
}

int main() {
	cin >> n;
	v[0] = {1,0};
	for (int i = 1; i <= n; i++) {
		int t, p;
		cin >> t >> p;
		v[i] = { t,p };
	}
	cout << solve(0) << endl;
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19
[C++] 백준 1697 숨바꼭질  (0) 2020.05.19
[C++] 백준 12865 평범한 배낭  (2) 2020.05.18
[C++] 백준 15686 치킨 배달  (0) 2020.05.18
[C++] 백준 16236 아기 상어  (0) 2020.05.18

+ Recent posts