문제 링크


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/14891

 

14891번: 톱니바퀴

총 8개의 톱니를 가지고 있는 톱니바퀴 4개가 아래 그림과 같이 일렬로 놓여져 있다. 또, 톱니는 N극 또는 S극 중 하나를 나타내고 있다. 톱니바퀴에는 번호가 매겨져 있는데, 가장 왼쪽 톱니바퀴

www.acmicpc.net

 

문제 접근


 주어지는 톱니 바퀴는 반시계방향, 시계방향으로 회전할 수 있으며, 옆에 다른 톱니바퀴의 회전에 영향을 줄 수 있다. 위의 톱니 바퀴를 수로 표현하면 10101111입니다. 각 숫자를 원형큐로 구현하여 회전을 쉽게 구현할 수 있습니다. 저는 원형큐가 익숙하지 않아서 보이는 그대로 비트 연산을 통해 구현하였습니다. 따라서 비트 연산으로 설명을 할 것인데, 리스트로 구현한 코드도 같이 올리도록 하겠습니다.

 

양 옆의 톱니 바퀴의 회전을 고려하기 전에, 기본 회전 코드를 먼저 짜는게 좋습니다.

 

<시계 방향 회전>

10101111 -> 01010111 (>> 연산만 한 결과 temp_bit = 1) -> 11010111 (temp_bit을 2^7로 올린 결과)

temp_bit = 1

>> 연산과 2^0 자릿수의 1을 다시 2^7으로 올리는 코드 구현

if (direct == 1) { // 시계 방향(오른쪽으로, 7번째 비트 저장 필요)
		temp_bit = get_bit(g, 7) << 7;
		gear = gear >> 1;
		gear |= temp_bit;
	}

<반시계 방향 회전>

10101111 -> 01011110 (<< 연산만 한 결과, temp_bit = 1) -> 01011111 (temp_bit을 2^0으로 올린 결과)

<< 연산과 2^7 자릿수를 다시 2^0으로 내리는 코드 구현

else if (direct == -1) { // 반시계 방향(왼쪽으로, 0번째 비트 저장 필요)
		temp_bit = get_bit(g, 0);
		gear = gear << 1;
		gear |= temp_bit;
	}

 

여기서 get_bit(g, idx)는 g라는 2진수 값에서 idx번째 자릿수를 얻는 연산입니다. 코드는 아래와 같습니다.

int get_bit(int g, int idx) {
	int temp = 1 << (7 - idx);
	int gear_bit = (gears[g] & temp);
	while (gear_bit > 1) {
		gear_bit = gear_bit >> 1;
	}
	return gear_bit;
}

 

자, 이제 g번째 바퀴를 회전시킬 때, 맞닿은 톱니 바퀴의 값이 반대인 경우, 해당 바퀴도 반대 방향으로 회전해주는 코드를 작성해볼게요.

 

i번 바퀴로 설명하겠습니다.

1. (i - 1)번 바퀴의 2번 비트가 (i)번 바퀴의 6번 비트와 다르다면 회전

2. (i + 1)번 바퀴의 6번 비트가 (i)번 바퀴의 2번 비트와 다르다면 회전

이 두 가지 조건에 visit 배열을 통한 바퀴 방문 여부, (i - 1), (i + 1) 인덱스 참조가 가능한지의 조건만 더해지면 재귀 함수를 완성할 수 있습니다. 설명과 아래 코드를 비교해보세요.

// g번째 바퀴의 g_idx와 ng번째 바퀴의 ng_idx가 같으면 true, 아니면 false
bool is_samebit(int g, int g_idx, int ng, int ng_idx) {
	return get_bit(g, g_idx) == get_bit(ng, ng_idx);
}

int rotate_gear(int g, int d) {
	visit[g] = 1;
	int rotated_gear = rotate(g, d);
	// 왼쪽 기어 확인
	if ((g - 1) >= 0 && !visit[g - 1] && !is_samebit(g, 6, g - 1, 2))
		rotate_gear(g - 1, d * -1);
	if ((g + 1) < NUM_GEARS && !visit[g + 1] && !is_samebit(g, 2, g + 1, 6))
		rotate_gear(g + 1, d * -1);
	gears[g] = rotated_gear;
	return 0;
}

중요한 점은 회전을 먼저 하고 검사를 하면 안된다는 것입니다. 회전하기 전에 각 맞닿은 비트를 검사해놓고, 회전은 나중에 해야합니다. 문제를 잘 읽어보시면 이해가 가실겁니다.

 

비트 연산이 햇갈리시면 #include <bitset>을 하셔서 bitset<8>(gears[0])를 이용해서 직접 비트를 출력해보시면 도움이 많이 됩니다.

 

소스 코드


<비트로 구현>

#include <bitset>
#include <cstring>
#include <iostream>
#include <algorithm>
#define NUM_GEARS 4
using namespace std;
int gears[NUM_GEARS], K, visit[NUM_GEARS];

int get_bit(int g, int idx) {
	int temp = 1 << (7 - idx);
	int gear_bit = (gears[g] & temp);
	while (gear_bit > 1) {
		gear_bit = gear_bit >> 1;
	}
	return gear_bit;
}

int rotate(int g, int direct) {
	int temp_bit, gear = gears[g];
	if (direct == 1) { // 시계 방향(오른쪽으로, 7번째 비트 저장 필요)
		temp_bit = get_bit(g, 7) << 7;
		gear = gear >> 1;
		gear |= temp_bit;
	}
	else if (direct == -1) { // 반시계 방향(왼쪽으로, 0번째 비트 저장 필요)
		temp_bit = get_bit(g, 0);
		gear = gear << 1;
		gear |= temp_bit;
	}
	return gear;
}

// g번째 바퀴의 g_idx와 ng번째 바퀴의 ng_idx가 같으면 true, 아니면 false
bool is_samebit(int g, int g_idx, int ng, int ng_idx) {
	return get_bit(g, g_idx) == get_bit(ng, ng_idx);
}

int rotate_gear(int g, int d) {
	visit[g] = 1;
	int rotated_gear = rotate(g, d);
	if ((g - 1) >= 0 && !visit[g - 1] && !is_samebit(g, 6, g - 1, 2))
		rotate_gear(g - 1, d * -1); // 왼쪽 기어 확인
	if ((g + 1) < NUM_GEARS && !visit[g + 1] && !is_samebit(g, 2, g + 1, 6))
		rotate_gear(g + 1, d * -1); // 오른쪽 기어 확인
	gears[g] = rotated_gear;
	return 0;
}

int get_score() {
	int ret = 0, bit, coefficient = 1;
	for (int i = 0; i < NUM_GEARS; ++i) {
		bit = get_bit(i, 0); // 0번째 비트 = 12시 방향의 비트
		ret += coefficient * bit;
		coefficient *= 2;
	}
	return ret;
}

int main() {
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	char a;
	for (int i = 0; i < NUM_GEARS; ++i) {
		for (int j = 0; j < 8; ++j) {
			cin >> a;
			a -= '0';
			gears[i] |= (a << (7 - j));
		}
	}
	cin >> K;
	int g, d;
	for (int k = 0; k < K; ++k) {
		memset(visit, 0, sizeof(visit));
		cin >> g >> d;
		rotate_gear(g - 1, d);
	}
	cout << get_score();
	return 0;
}

 

<리스트로 구현>

#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define NUM_GEARS 4
using namespace std;
int gears[NUM_GEARS][8], K, visit[NUM_GEARS];
vector<pair<int, int>> ops;

// i번째 톱니바퀴를 d방향으로 회전
void rotate(int i, int d) {
	int temp;
	if (d == 1) { // 시계방향, 우측
		temp = gears[i][7];
		for (int j = 7; j >= 1; --j)
			gears[i][j] = gears[i][j - 1];
		gears[i][0] = temp;
	}
	else if (d == -1) { // 반시계방향, 좌측
		temp = gears[i][0];
		for (int j = 0; j + 1 < 8; ++j)
			gears[i][j] = gears[i][j + 1];
		gears[i][7] = temp;
	}
}

// g번째 바퀴의 gi번째 비트와 ng번째 바퀴의 ngi번째 비트를 비교한다.
bool is_different(int g, int gi, int ng, int ngi) {
	return gears[g][gi] != gears[ng][ngi]; // 같으면 true, 다르면 false
}

void check_rotate(int i, int d) {
	visit[i] = 1;
	ops.push_back({ i, d });
	int left = i - 1, right = i + 1;
	//cout << i << "번째 바퀴 => ";
	if (left >= 0) { // 왼쪽이 존재한다면
		//cout << "나의 6번째 값:" << gears[i][6] << " 왼쪽의 2번째 값:" << gears[i][2] << "\n";
		if (!visit[left] && is_different(i, 6, left, 2)) {
			check_rotate(left, d * -1);
		}
	}

	if (right < NUM_GEARS) {
		//cout << "나의 2번째 값:" << gears[i][2] << " 오른쪽의 6번째 값:" << gears[i][6] << "\n";
		if (!visit[right] && is_different(i, 2, right, 6)) {
			check_rotate(right, d * -1);
		}
	}
}

int get_score() {
	int ret = 0, sum_check = 1;
	for (int g = 0; g < NUM_GEARS; ++g) {
		if (gears[g][0] == 1) ret += sum_check;
		sum_check *= 2;
	}
	return ret;
}

int main() {
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	char input;
	for (int i = 0; i < NUM_GEARS; ++i)
		for (int j = 0; j < 8; ++j) {
			cin >> input;
			gears[i][j] = input - '0';
		}

	cin >> K;
	int g, d;
	for (int k = 0; k < K; ++k) {
		memset(visit, 0, sizeof(visit));
		cin >> g >> d;
		check_rotate(g - 1, d);
		for (int i = 0; i < ops.size(); ++i) {
			rotate(ops[i].first, ops[i].second);
		}
		ops.clear();
	}
	cout << get_score();
	return 0;
}

개발 환경 : VS2019

질문, 지적 환영합니다.

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

[C++] 백준 13460 구슬 탈출 2  (0) 2020.05.26
[C++] 백준 15684 사다리 조작  (0) 2020.05.25
[C++} 백준 14890 경사로  (0) 2020.05.25
[C++] 백준 15683 감시  (0) 2020.05.20
[C++] 백준 14500 테트로미노  (0) 2020.05.20

문제 링크


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

 

14890번: 경사로

첫째 줄에 N (2 ≤ N ≤ 100)과 L (1 ≤ L ≤ N)이 주어진다. 둘째 줄부터 N개의 줄에 지도가 주어진다. 각 칸의 높이는 10보다 작거나 같은 자연수이다.

www.acmicpc.net

 

 

문제 접근


점 (i, j)와 (i, j + 1)의 비교의 경우의 수는 다음과 같다.

1. 높이의 차가 2 이상인 경우

2. 같은 높이인 경우

3. 차가 1일때, 오르막인 경우

4. 차가 1일때, 내리막인 경우

 

높이의 차가 2인 경우, 해당 길은 지날 수 없다.

까다로운 부분은 오르막, 내리막의 경우인데, 낮은 지대에 경사로의 크기 L을 수용할 만한 공간이 있음을 체크해야한다.

 

- (i, j + 1)이 오르막인 경우, (i, j)가 더 낮은 지대이므로 (i, j)부터 뒤로 가면서 낮은 지대의 갯수가 L보다 크거나 같으면 이동 가능

 

- (i, j + 1)이 내리막인 경우, (i, j + 1)이 더 같은 지대이므로, (i, j + 1)부터 앞으로 가면서 낮은 지대의 갯수가 L보다 크거나 같으면 이동 가능

 

또 다른 중요한 점은 내리막과 오르막이 연달아 있는 경우에서 경사로를 겹쳐서 놓는 경우는 허가되지 않는다는 것이다. 따라서 visit 배열을 통해 경사로가 놓인 위치를 표시해줘야한다.

 

 

소스 코드


#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 100
using namespace std;
int N, L, map[MAX][MAX], visit[MAX];

void input() {
	cin >> N >> L;
	for (int i = 0; i < N; ++i)
		for (int j = 0; j < N; ++j)
			cin >> map[i][j];
}

int solve() {
	int differ, ret = 0, low_area = 0;
	for (int i = 0; i < N; ++i) {
		memset(visit, 0, sizeof(visit));
		// 가로 검사
		for (int j = 1; j < N; ++j) {
			differ = map[i][j - 1] - map[i][j];
			if (abs(differ) > 1) break; // 차이가 2 이상이면 갈 수 있는 길이 아님
			
			// 오르막인 경우
			if (differ < 0) {
				low_area = 0;
				for (int l = j - 1; l >= 0; --l) { // j가 높은 지대이므로 j-1이전을 카운트
					if (visit[l] || map[i][l] != map[i][j - 1]) break;
					++low_area;
				}
				if (low_area >= L) { // 다리놓을 공간이 충분하므로 L개만큼 방문 표시하자
					low_area = L;
					for (int l = j - 1; l >= 0 && low_area > 0; --l, --low_area)
						visit[l] = 1;
				}
				else {
					break;
				}
			}

			// 내리막인 경우
			else if (differ > 0) { // j부터 낮은 지대이다.
				low_area = 0;
				for (int l = j; l < N; ++l) {
					if (visit[l] || map[i][l] != map[i][j]) break;
					++low_area;
				}
				if (low_area >= L) { // 다리놓을 공간이 충분하므로 L개만큼 방문 표시하자
					low_area = L;
					for (int l = j; l < N && low_area > 0; ++l, --low_area)
						visit[l] = 1;
				}
				else break;
			}
			// break되지 않고 여기까지왔다면 갈 수 있는 길임
			if (j == N - 1) ++ret;
		}
		memset(visit, 0, sizeof(visit));
		// 세로 검사
		for (int j = 1; j < N; ++j) {
			differ = map[j - 1][i] - map[j][i];
			if (abs(differ) > 1) break; // 차이가 2 이상이면 갈 수 있는 길이 아님

			// 오르막인 경우
			if (differ < 0) {
				low_area = 0;
				for (int l = j - 1; l >= 0; --l) { // j가 오르막이므로 j-1부터 본다.
					if (visit[l] || map[l][i] != map[j - 1][i]) break;
					++low_area;
				}
				if (low_area >= L) { // 다리놓을 공간이 충분하므로 L개만큼 방문 표시하자
					low_area = L;
					for (int l = j - 1; l >= 0 && low_area > 0; --l, --low_area)
						visit[l] = 1;
				}
				else break;
			}

			// 내리막인 경우
			else if (differ > 0) {
				low_area = 0;
				for (int l = j; l < N; ++l) { // j부터 낮은 지대임
					if (visit[l] || map[l][i] != map[j][i]) break;
					++low_area;
				}
				if (low_area >= L) { // 다리놓을 공간이 충분하므로 L개만큼 방문 표시하자
					low_area = L;
					for (int l = j; l < N && low_area > 0; ++l, --low_area)
						visit[l] = 1;
				}
				else break;
			}
			// break되지 않고 여기까지왔다면 갈 수 있는 길임
			if (j == N - 1) ++ret;
		}
	}
	return ret;
}

int main() {
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	input();
	cout << solve();
	return 0;
}

개발 환경 : VS2019

질문, 지적 환영합니다.

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

[C++] 백준 15684 사다리 조작  (0) 2020.05.25
[C++] 백준 14891 톱니바퀴  (0) 2020.05.25
[C++] 백준 15683 감시  (0) 2020.05.20
[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19

문제 링크


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

 

15683번: 감시

스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감��

www.acmicpc.net

 

 

접근 방법


아래와 같은 순서로 문제를 접근하였다.

1. CCTV의 타입(1~5), 좌표, 방향을 cctv 백터에 push

2. 모든 CCTV의 방향 조합 문제로 변형 -> 백트래킹 시도 (CCTV 타입도 고려해야함)

3. 백트래킹하며 CCTV의 타입과 방향, 좌표를 selected 벡터에 push

4. selected 크기가 cctv 전체 갯수 도달시 현재까지 선택된 CCTV 타입, 방향, 좌표 정보를 가지고

map에 감시 영역을 채운다.

5. 최소 사각지대의 개수를 반환한다.

 

단순히 따지자면 CCTV의 방향의 조합 문제인데, CCTV마다 한 번에 감시할 수 있는 방향이 다르기 때문에 CCTV 타입도 고려하면서 진행하였다.

 

방향에 대한 접근은 위와 같이 하였다. 타입 1 CCTV는 위와 같은 방향 접근으로 하며, 다른 타입도 위의 방향을 기반으로 정의하였다.

 

2번 타입은 위 사진과 같이 접근하였다. 정리하면 a번째 방향 + a+2번째 방향이다.

 

3번은 위와 같다. 정리하면 a번째 방향 + a+1번째 방향이다.

 

4번은 위와 같다. 정리하면 a번째 방향 + a-1번째 방향 + a + 1번째 방향이다.

 

5번은 모든 방향이다.

 

cctv의 정보를 담기 위해 구조체를 정의하였는데, CCTV의 타입, 방향, 좌표를 저장하였다.

각 CCTV 타입마다 방향의 개수가 다르므로 num_direct를 선언하였고, 방향에 대한 정보는 DIRECTION에 담았다.

typedef struct cctv {
	int type;
	int direct;
	int y, x;
};
int num_direct[NUM_TYPE] = { 0, 4, 2, 4, 4, 1 }; // 1번: 4개, 2번: 2개, 3번: 4개, 4번: 2개, 5번: 1개
const coord DIRECTION[4] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };

 

위의 방향 접근법으로 해당 방향 라인을 체크해주는 함수를 정의하였다.

(여기서 tmp는 map의 복사본입니다.)

void fill_line(vector<vector<int>> &tmp, int sy, int sx, int direct) {
	while (isRanged(sy, sx) && map[sy][sx] != 6) {
		tmp[sy][sx] = 9;
		sy += DIRECTION[direct].y;
		sx += DIRECTION[direct].x;
	}
}

위의 함수는 tmp라는 2차원 배열에 시작 위치 (sy,sx)를 기준으로 direct 방향으로 모든 영역을 9로 표시한다. (벽을 만나면 멈춤)

위 함수를 이용하여 cctv의 타입에 따라서 map을 칠해주는 함수를 정의하였다. 내용은 아래와 같다.

void fill_map(vector<vector<int>> &tmp, cctv c) {
	if (c.type == 1) {
		fill_line(tmp, c.y, c.x, c.direct);
	}
	else if (c.type == 2) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, c.direct + 2);
	}
	else if (c.type == 3) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, (c.direct + 1) % 4);
	}
	else if (c.type == 4) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, (c.direct == 0 ? 3 : c.direct - 1));
		fill_line(tmp, c.y, c.x, (c.direct + 1) % 4);
	}
	else {
		for (int direct = 0; direct < 4; ++direct)
			fill_line(tmp, c.y, c.x, direct);
	}
}

위 함수는 tmp라는 2차원 배열을 받아서 cctv type c와 방향에 따라서 map에 감시 구역을 표시해주는 함수이다.

 

소스 코드


#include <vector>
#include <climits>
#include <iostream>
#include <algorithm>
#define MAX 8
#define NUM_TYPE 6
using namespace std;
typedef struct cctv {
	int type;
	int direct;
	int y, x;
};
typedef struct coord {
	int y, x;
};
int N, M;
vector<vector<int>> map(MAX, vector<int>(MAX, 0)), tmp(MAX, vector<int>(MAX, 0));
vector<cctv> cctv_list, selected;
int num_direct[NUM_TYPE] = { 0, 4, 2, 4, 4, 1 }; // 1번: 4개, 2번: 2개, 3번: 4개, 4번: 2개, 5번: 1개
const coord DIRECTION[4] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };

void input() {
	cin >> N >> M;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			cin >> map[i][j];
			if (map[i][j] != 0 && map[i][j] != 6) {
				cctv_list.push_back({map[i][j], 0, i, j}); // {CCTV종류, 방향, y좌표, x좌표}
			}
		}
	}
}

bool isRanged(int y, int x) {
	if (y >= N || x >= M || y < 0 || x < 0) return false;
	return true;
}

void fill_line(vector<vector<int>> &tmp, int sy, int sx, int direct) {
	while (isRanged(sy, sx) && map[sy][sx] != 6) {
		tmp[sy][sx] = 9;
		sy += DIRECTION[direct].y;
		sx += DIRECTION[direct].x;
	}
}

void fill_map(vector<vector<int>> &tmp, cctv c) {
	if (c.type == 1) {
		fill_line(tmp, c.y, c.x, c.direct);
	}
	else if (c.type == 2) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, c.direct + 2);
	}
	else if (c.type == 3) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, (c.direct + 1) % 4);
	}
	else if (c.type == 4) {
		fill_line(tmp, c.y, c.x, c.direct);
		fill_line(tmp, c.y, c.x, (c.direct == 0 ? 3 : c.direct - 1));
		fill_line(tmp, c.y, c.x, (c.direct + 1) % 4);
	}
	else {
		for (int direct = 0; direct < 4; ++direct)
			fill_line(tmp, c.y, c.x, direct);
	}
}

int count_area(vector<vector<int>> &tmp) {
	int ret = 0;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			if (tmp[i][j] == 0) ++ret;
		}
	}
	return ret;
}

vector<vector<int>> copy_map(vector<vector<int>> &from, vector<vector<int>> &to) {
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			to[i][j] = from[i][j];
		}
	}
	return to;
}

int nCm(int cnt, int prev) {
	if (cnt == cctv_list.size()) {
		tmp = copy_map(map, tmp);
		for (int i = 0; i < selected.size(); ++i)
			fill_map(tmp, selected[i]);
		int ret = count_area(tmp);
		return ret;
	}
	int ret = INT_MAX;
	for (int next = prev + 1; next < cctv_list.size(); ++next) {
		for (int direct = 0; direct < num_direct[cctv_list[next].type]; ++direct) {
			selected.push_back({cctv_list[next].type, direct, cctv_list[next].y, cctv_list[next].x});// 현재 cctv type, direct를 저장한다.
			ret = min (ret, nCm(cnt + 1, next));
			selected.pop_back();// 칠한 map을 다시 되돌린다.
		}
	}
	return ret;
}

int main() {
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	input();
	cout << nCm(0, -1);
	return 0;
}

 

개발 환경은 VS 2019입니다.

질문, 지적, 덧글 환영합니다!!

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

[C++] 백준 14891 톱니바퀴  (0) 2020.05.25
[C++} 백준 14890 경사로  (0) 2020.05.25
[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19
[C++] 백준 1697 숨바꼭질  (0) 2020.05.19

문제 링크


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