문제 : https://www.acmicpc.net/problem/1194


비트마스킹 BFS 그래프탐색

[문제해석]

 

  '0' 지점에서 '1' 지점까지 탐색을 진행하는데 'a','b','c','d','e','f' 는 열쇠고 'A','B','C','D','E','F'는 각각 소문자 열쇠로 열 수 있는 문이다. 문을 열려면 열쇠가 있는 지점을 먼저 방문해서 열쇠를 가지고 방문해야된다.

 

[알고리즘풀이]

 

 전형적인 비트마스킹을 활용한 그래프 탐색 문제로 열쇠가 6 종류 밖에 없는 점에 집중해 비트마스킹으로 현재 어떤 열쇠들을 가지고 있는지 표현해주면 된다. 

 visited[X][Y][curS] := (X, Y) 지점에 curS 열쇠 상태로 방문했을 때 최소 이동 횟수 라고 정의하면 문제를 쉽게 해결할 수 있다.

 

 1) 다음에 이동할 지점이 '.' 거나 '1' 이면 현재 이동 횟수 + 1 이 해당 지점에 현재 상태로 방문한 가장 작은 값이라면 이동을 진행한다.

 

 2) 다음에 이동할 지점이 열쇠가 있고 현재 이동 횟수 + 1 이 해당 지점에 현재 상태로 방문한 가장 작은 값이라면 이동을 진행한다. 그 후, 현재 상태에 해당 지점의 열쇠를 갱신해준다.

 | (OR) 연산을 이용하면 쉽게 갱신할 수 있다.

 ( curS | (1 << key) ) 

 

 3) 다음에 이동할 지점이 문이 있고 현재 이동 횟수 + 1 이 해당 지점에 현재 상태로 방문한 가장 작은 값이고, 현재 열쇠 상태가 해당 문을 열 수 있다면 이동을 진행한다.

 & (AND) 연산을 이용하면 현재 열쇠 상태로 문을 열 수 있는지 쉽게 확인할 수 있다.

 ( curS & (1 << door) )

 

[ 주의사항 ]

 

 열쇠가 총 6종류 이므로 비트마스킹을 (1 << 7) 로 진행해야되는데, (1 << 6) 으로 셋팅해서 개고생을 했다....! 조심, 또 조심!

 

[ 정답코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include<iostream>
#include<queue>
using namespace std;
 
struct position {
    int x, y, s, l;
};
 
const int INF = 9999999;
int r, c;
int visited[50][50][1 << 7];
int dx[4= { -1010 }, dy[4= { 0-101 };
char map[50][51];
 
bool checkIndex(int x, int y) {
    return (0 <= x && x < r && 0 <= y && y < c);
}
 
int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
 
    cin >> r >> c;
    for (int i = 0; i < r; i++)
        cin >> map[i];
    for (int i = 0; i < r; i++) // 초기화
        for (int j = 0; j < c; j++)
            for (int k = 0; k < (1 << 7); k++)
                visited[i][j][k] = INF;
 
    queue<position> q;
    for (int i = 0; i < r; i++) // 시작점 찾기
        for (int j = 0; j < c; j++)
            if (map[i][j] == '0') {
                visited[i][j][0= 0;
                q.push({ i, j, 00 });
                map[i][j] = '.';
            }

    while (!q.empty()) { // 탐색진행
        int curX = q.front().x, curY = q.front().y, curS = q.front().s, curL = q.front().l;
        q.pop();
        if (map[curX][curY] == '1'continue;
 
        for (int i = 0; i < 4; i++) {
            int nextX = curX + dx[i], nextY = curY + dy[i];
            if (!checkIndex(nextX, nextY)) continue;
            if (map[nextX][nextY] == '#'continue;
            if ((map[nextX][nextY] == '.' || map[nextX][nextY] == '1'&& curL + 1 < visited[nextX][nextY][curS]) { // 그냥 갈 수 있는 길
                visited[nextX][nextY][curS] = curL + 1;
                q.push({ nextX, nextY, curS, curL + 1 });
            }
            else if ('a' <= map[nextX][nextY] && map[nextX][nextY] <= 'f' && curL + 1 < visited[nextX][nextY][curS]) {
                int key = (map[nextX][nextY] - 'a'+ 1;
                visited[nextX][nextY][curS] = curL + 1;
                q.push({ nextX, nextY, curS | (1 << key), curL + 1 });
            }
            else if ('A' <= map[nextX][nextY] && map[nextX][nextY] <= 'F' && curL + 1 < visited[nextX][nextY][curS]) {
                int door = (map[nextX][nextY] - 'A'+ 1;
                if (curS & (1 << door)) {
                    visited[nextX][nextY][curS] = curL + 1;
                    q.push({ nextX, nextY, curS, curL + 1 });
                }
            }
        }
    }
    
    int ans = INF; // '1' 인 지점에 방문한 값 중 최소값 찾기
    for (int i = 0; i < r; i++)
        for (int j = 0; j < c; j++)
            if (map[i][j] == '1')
                for (int k = 0; k < (1 << 7); k++)
                    ans = min(ans, visited[i][j][k]);
    if (ans == INF) ans = -1;
    cout << ans << '\n';
}
cs

 

'Problem Solving > BOJ' 카테고리의 다른 글

[BOJ] 2517 : 달리기  (0) 2020.08.14
[BOJ] 10167 : 금광  (0) 2020.08.13
[BOJ] 17025 : Icy Perimeter  (0) 2020.07.30
[BOJ] 15898 : 피아의 아틀리에 ~신비한 대회의 연금술사~  (0) 2020.07.27
[BOJ] 15906 : 변신 이동 게임  (0) 2020.07.06

문제 : https://www.acmicpc.net/problem/17025


BFS DFS 탐색

[문제해석]

'.' 은 빈 공간, '#' 은 아이스크림을 나타낼 때, #이 연결된 부분은 하나의 아이스크림 덩어리가 된다. 이때, 아이스크림 덩어리의 Area(영역너비)와 Perimeter(둘레)를 구해서 영역너비가 가장 크고, 가장 큰 값이 여러개라면 가장 둘레가 작은 아이스크림 덩어리를 찾아달라.

 

[알고리즘]

 BFS 탐색을 진행하며 아이스크림 덩어리를 하나하나 탐색한다. 이때, 영역너비는 BFS 탐색을 진행하며 탐색하는 칸의 수가 영역너비가 된다. 둘레는 현재 탐색 중인 '#' 의 위, 아래, 오른쪽, 왼쪽을 탐색해 범위 밖이거나 '.' 라면 경계가 존재하므로 둘레로 카운트한다.

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

using namespace std;

int N, dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, -1, 0, 1 };
int ansArea = -1, ansPerimeter = -1;
char map[1000][1001];
bool visited[1000][1000];
vector<pair<int, int>> ans;

bool checkRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

int getPerimeter(int x, int y) {
	int res = 0;
	if (!checkRange(x + 1, y) || (checkRange(x + 1, y) && map[x + 1][y] == '.')) res++;
	if (!checkRange(x, y + 1) || (checkRange(x, y + 1)&& map[x][y + 1] == '.')) res++;
	if (!checkRange(x - 1, y) || (checkRange(x - 1, y) && map[x - 1][y] == '.')) res++;
	if (!checkRange(x, y - 1) || (checkRange(x, y - 1) && map[x][y - 1] == '.')) res++;
	return res;
}

void BFS(int x, int y) {
	queue<pair<int, int>> q;
	q.push({ x, y });
	visited[x][y] = 1;

	int area = 0, perimeter = 0;
	while (!q.empty()) {
		int curX = q.front().first, curY = q.front().second;
		area++, perimeter += getPerimeter(curX, curY);
		q.pop();
		for (int i = 0; i < 4; i++) {
			int nextX = curX + dx[i], nextY = curY + dy[i];
			if (checkRange(nextX, nextY) && !visited[nextX][nextY] && map[nextX][nextY] == '#') {
				q.push({ nextX, nextY });
				visited[nextX][nextY] = 1;
			}
		}
	}
	if (ansArea < area) {
		ansArea = area;
		ansPerimeter = ansPerimeter;
	}
	else if (ansArea == area) {
		ansPerimeter = min(ansPerimeter, perimeter);
	}

	return;
}
int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> N;
	for (int i = 0; i < N; i++)
		cin >> map[i];

	for (int i = 0; i < N; i++)
		for (int j = 0; j < N; j++)
			if (map[i][j] == '#' && !visited[i][j]) BFS(i, j);
	
	cout << ansArea << ' ' << ansPerimeter << '\n';
	return 0;
}

 

문제 : https://www.acmicpc.net/problem/15898


#구현 #시뮬레이션

[ 문제해석 ]

5 x 5 가마에 4 x 4 크기의 재료들을 넣어서 폭탄을 제조한다. 이때, 4 x 4 크기의 재료는 가마의 격자를 벗어날 수 없고, 회전해서 넣을 수 있고 서로 다른 재료를 3가지 가마에 넣는다.

 

[ 알고리즘핵심 ]

4 x 4 크기의 재료는 최대 10개이고 회전해서 넣을 수 있으므로 최대 40개(회전시킨재료포함)의 재료에서 3가지 재료를 뽑는다고 생각해도 된다. 그 후, 이 재료들을 5 x 5 가마에 넣어야되는데 이때도 가마의 (0, 0) / (0, 1) / (1, 0) / (1, 1) 에 재료를 넣는 경우를 제외하고는 가마를 다 벗어난다.

 따라서, 백트레킹을 이용해 40개에서 3개를 뽑고, 4가지 위치 중에 하나를 고르는 모든 경우의 수를 구해도 복잡도가 40C3 * 4^3 밖에 나오지 않는다. 즉, 1초 안에 가능한 연산이다.

 

[ 알고리즘풀이 ]

1. 입력을 받으면 시계방향으로 90도, 180도, 270도 회전한 상태도 같이 effect, color 배열에 저장해준다.

2. 백트레킹1 - 백트레킹을 이용해 재료 3개를 뽑는다. 

3. 백트레킹2 - 재료 3개를 다 뽑은 상태에서, 재료 3개의 넣는 위치를 구한다.

4. 플레이게임 - 'W'로 초기화된 가마를 만든 후, 3가지 재료를 정해진 위치에 넣고 red, blue, green, yellow 효능 값을 구해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include<iostream>
#include<algorithm>
#include<vector>
 
using namespace std;
 
int n, ans;
int dx[4= { 0011 }, dy[4= { 0101 };
int effect[40][4][4];
char color[40][4][4];
bool choosenMaterial[10];
vector<int> v, v2;
 
void turnClockwise(int index, int turn) {
    if (turn == 3return;
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++) {
            effect[index + 1][i][j] = effect[index][3 - j][i];
            color[index + 1][i][j] = color[index][3 - j][i];
        }
    turnClockwise(index + 1, turn + 1);
}
 
void playGame(void) {
    int effectMap[5][5= {};
    char colorMap[5][5= {};
    for (int i = 0; i < 5; i++)
        for (int j = 0; j < 5; j++) colorMap[i][j] = 'W';
 
    for (int i = 0; i < 3; i++) {
        // Map에 재료를 넣자.
        int sX = dx[v2[i]], sY = dy[v2[i]];
 
 
        for(int j = 0; j < 4;j++)
            for (int k = 0; k < 4; k++) {
                if (color[v[i]][j][k] != 'W') colorMap[sX + j][sY + k] = color[v[i]][j][k];
                effectMap[sX + j][sY + k] += effect[v[i]][j][k];
                if (effectMap[sX + j][sY + k] < 0) effectMap[sX + j][sY + k] = 0;
                if (effectMap[sX + j][sY + k] > 9) effectMap[sX + j][sY + k] = 9;
            }
    }
    int red = 0, blue = 0, green = 0, yellow = 0;
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++)
            if (colorMap[i][j] == 'R') red += effectMap[i][j];
            else if (colorMap[i][j] == 'B') blue += effectMap[i][j];
            else if (colorMap[i][j] == 'G') green += effectMap[i][j];
            else if (colorMap[i][j] == 'Y') yellow += effectMap[i][j];
    }
 
    ans = max(ans, 7 * red + 5 * blue + 3 * green + 2 * yellow);
    return;
}
// 뽑은 3개의 재료를 (0, 0), (0, 1), (1, 0), (1, 1) 중 어디에 놓을지 정한다.
void backtracking2(void) {
    if (v2.size() == 3) {
        playGame();
        return;
    }
    for (int i = 0; i < 4; i++) {
        v2.push_back(i);
        backtracking2();
        v2.pop_back();
    }
}
// n개의 재료를 회전시킨 4 * n 경우의 수 중 3개를 뽑는다.
void backtracking1(void) {
    if (v.size() == 3) {
        backtracking2();
        return;
    }
    for (int i = 0; i < 4 * n; i++) {
        if (choosenMaterial[i / 4]) continue;
        choosenMaterial[i / 4= 1;
        v.push_back(i);
        backtracking1();
        v.pop_back();
        choosenMaterial[i / 4= 0;
    }
}
 
int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
 
    cin >> n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < 4; j++)
            for (int k = 0; k < 4; k++)
                cin >> effect[4 * i][j][k];
        for (int j = 0; j < 4; j++)
            for (int k = 0; k < 4; k++)
                cin >> color[4 * i][j][k];
        turnClockwise(4 * i, 0);
    }
 
    backtracking1();
    cout << ans << '\n';
    return 0;
}
cs

 

'Problem Solving > BOJ' 카테고리의 다른 글

[BOJ] 1194 : 달이 차오른다, 가자.  (0) 2020.08.07
[BOJ] 17025 : Icy Perimeter  (0) 2020.07.30
[BOJ] 15906 : 변신 이동 게임  (0) 2020.07.06
[BOJ] 18809 : Gaaaaaaaaaarden  (0) 2020.07.02
[BOJ] 3109 : PLINOVOD  (0) 2020.07.01

문제 : https://www.acmicpc.net/problem/15906


BFS DP

문제 해석

 (1,1) 에서 (r,c) 로 최대한 빠르게 이동하고 싶다. 이때, 일반상태에서는 상하좌우로 한 칸 씩만 이동이 가능하고, t 턴을 소모해 변신상태가 되면 상하좌우 중 '#' 이 있는 워프 지역으로 한 번에 이동이 가능하다.

문제 핵심

 일반 상태로도 진행할 수 있고, 변신 상태로도 탐색을 진행할 수 있으므로 현재 지점에서 일반 이동 과 변신을 시킨 후 변신 이동을 모두 진행해야한다.

알고리즘

 Dp[X][Y][S] := ( X, Y ) 지점에 상태 S로 도착하는데 필요한 최소한의 턴 수.

--> 현재 지점에서 nextX, nextY 로 이동할 때 Dp 값과 현재 시간을 비교해서 최소한의 턴 수 가 아니라면 더 탐색을 진행하지 않아도 된다.

 먼저 현재 지점에서 일반 이동을 해보고, 변신 상태가 아니라면 변신을 한 후 변신 이동을 해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include<iostream>
#include<queue>
#include<algorithm>
 
using namespace std;
 
struct box {
    int x, y, time;
    bool change;
};
 
int N, t, r, c;
int dx[4= { -1 , 010 }, dy[4= { 0-101 };
const int INF = 999999;
char map[501][501];
int dp[501][501][2];
queue<box> q;
 
bool check(int x, int y) {
    return (0 <= x && x < N && 0 <= y && y < N);
}
void normalMove(int curX, int curY, int curT, bool curS) {
    for (int i = 0; i < 4; i++) {
        int nextX = curX + dx[i], nextY = curY + dy[i];
        if (!check(nextX, nextY)) continue;
        if (curT + 1 < dp[nextX][nextY][curS]) {
            dp[nextX][nextY][curS] = curT + 1;
            q.push({ nextX, nextY, curT + 1, curS });
        }
    }
}
 
void specialMove(int curX, int curY, int curT, bool curS) {
    for (int i = 0; i < 4; i++) {
        int nextX = curX + dx[i], nextY = curY + dy[i];
        while (check(nextX, nextY) && map[nextX][nextY] != '#') {
            nextX += dx[i], nextY += dy[i];
        }
        if (check(nextX, nextY) && curT + 1 < dp[nextX][nextY][curS]) {
            dp[nextX][nextY][curS] = curT + 1;
            q.push({ nextX, nextY, curT + 1, curS });
        }
    }
}
 
int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
 
    cin >> N >> t >> r >> c;
    r--, c--;
    for (int i = 0; i < N; i++)
        cin >> map[i];
 
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < 2; k++)
                dp[i][j][k] = INF;
 
    q.push({ 0000 });
    dp[0][0][0= 0;
 
    while (!q.empty()) {
        int curX = q.front().x, curY = q.front().y;
        int curT = q.front().time;
        bool curS = q.front().change;
        q.pop();
        // 목적지 도착
        if (curX == r && curY == c) continue;
        // 그냥 이동
        normalMove(curX, curY, curT, false);
        // 변신해서 이동!
        if(curS == false) curT += t; // 변신
        specialMove(curX, curY, curT, true);
    }
    cout << min(dp[r][c][0], dp[r][c][1]) << '\n';
}
cs

 

+ Recent posts