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


문제해석

 Green, Red 배양액을 땅에 뿌리고, 배양액이 주변으로 퍼져나가며, Green / Red 배양액이 만나게 되면 Flower가 생긴다.

문제핵심

1. 가능한 땅 중에서 Green, Red 배양액을 무조건 다 뿌려야 된다.

--> Backtracking 을 이용해서 가능한 모든 경우를 구해봐야 한다.

2. Green, Red 배양액이 단순히 만나는 게 아니라 동일한 시간에 도달한 땅에서만 Flower 가 생긴다.

--> 단순히 BFS 탐색만을 진행하면 안 된다.

알고리즘

1. 입력받은 map에서 배양액을 뿌릴 수 있는 vitaminMap을 따로 모아둔다.

2. 백트레킹을 통해 vitaminMap에 Green, Red 배양액을 다 뿌린다.

3. BFS 탐색을 통해서 Green, Red 배양액을 뿌려나갈 건데 다음과 같은 stateMap을 이용한다.

pair<int, int> stateMap[i][j] = (i, j) 에서 { 현재 시간, 현재 상태 } 를 저장한다.

--> 초기 배양액을 뿌린 곳들만 stateMap[i][j] = { 0, Green || Red } 로 초기화한다.

--> BFS 탐색을 통해서 현재 지점에서 다음 지점들로 탐색을 진행해나가는데, 

● 다음 지점의 stateMap 의 상태가 EMPTY 면 Green, Red 배양액이 아직 뿌려지지 않은 Map이므로 Green, Red 배양액을 저장해준다.

 다음 지점의 stateMap 상태가 Green 이고 현재 상태가 Red 이고 같은 시간에 방문했다면 Flower 를 만듭니다.

 다음 지점의 stateMap 상태가 Red 이고 현재 상태가 Green 이고 같은 시간에 방문했다면 Flower 를 만듭니다.

알고리즘회고

 처음에는 stateMap 을 이용할 생각을 못하고, 현재 queue에 저장된 Green, Red 에서 다음 지점들을 다 탐색해나가며 nextGreen, nextRed 라고 다음 지점들을 다 따로 저장했다. 그 후, 따로 저장한 지점들을 비교하며 Flower가 만들어지는 지점들을 체크하고 아닌 지점들은 다시 queue에 Push 해주는 방식으로 BFS 탐색을 진행했다. 당연히 시간이 위의 알고리즘에 비해 오래 걸렸고 BarkingDog 님의 코드를 참고해서 알고리즘을 개선할 수 있었다.

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;

int N, M, G, R, ans;
int map[50][50]; // 0 은 호수 3은 레드 4는 그린
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, -1, 0, 1 };
vector<pair<int, int>> vitaminMap;
int vitamin[10]; // 0은 아무것도 안뿌림 1은 레드 2는 그린

const int EMPTY = 0;
const int RED = 3;
const int GREEN = 4;
const int FLOWER = 5;

bool check(int r, int c) {
	if (0 <= r && r < N && 0 <= c && c < M) return true;
	return false;
}

void BFS() {
	int result = 0;
	pair<int, int> state[50][50];
	queue<pair<int, int>> q;
	for (int i = 0; i < vitaminMap.size(); i++) {
		if (vitamin[i] == 0) continue;
		state[vitaminMap[i].first][vitaminMap[i].second] = { 0, vitamin[i] };
		q.push(vitaminMap[i]);
	}
	while (!q.empty()) {
		int curX = q.front().first, curY = q.front().second;
		int curT = state[curX][curY].first, curC = state[curX][curY].second;
		q.pop();
		if (curC == FLOWER) continue;
		for (int dir = 0; dir < 4; dir++) {
			int nX = curX + dx[dir], nY = curY + dy[dir];
			if (!check(nX, nY)) continue; // 범위 밖
			if (map[nX][nY] == 0) continue; // 호수
			if (state[nX][nY].second == EMPTY) {
				state[nX][nY] = { curT + 1, curC };
				q.push({ nX, nY });
			}
			else if (state[nX][nY].second == RED) {
				if (curC == GREEN && state[nX][nY].first == curT + 1) {
					result++;
					state[nX][nY].second = FLOWER;
				}
			}
			else if (state[nX][nY].second == GREEN) {
				if (curC == RED && state[nX][nY].first == curT + 1) {
					result++;
					state[nX][nY].second = FLOWER;
				}
			}
		}
	}
	ans = max(ans, result);
	return;
}

void Backtracking(int s, int curG, int curR) {
	if (curG == G && curR == R) {
		BFS();
		return;
	}
	for (int i = s; i < vitaminMap.size(); i++) {
		if (vitamin[i] == 0 && curG < G) {
			vitamin[i] = GREEN;
			Backtracking(i + 1, curG + 1, curR);
			vitamin[i] = 0;
		}
		if (vitamin[i] == 0 && curR < R) {
			vitamin[i] = RED;
			Backtracking(i + 1, curG, curR + 1);
			vitamin[i] = 0;
		}
	}
	return;
}

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

	cin >> N >> M >> G >> R;
	for (int i = 0; i < N; i++)
		for (int j = 0; j < M; j++){
			cin >> map[i][j];
			if (map[i][j] == 2) vitaminMap.push_back({ i, j });
		}

	Backtracking(0, 0, 0);

	cout << ans << '\n';
	return 0;
}

 

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


문제해석

 왼쪽끝에서 오른쪽끝으로 파이프를 연결해 길을 만들고 싶은데, 파이프끼리 겹치면 안되고 파이프는 오른쪽, 오른쪽 대각선 위, 오른쪽 대각선 아래 로 이어서 설치할 수 있다. 길을 최대 몇 개 만들 수 있을까?

문제핵심

 1. 현재 지점에서 파이프를 연결해 길을 만들 때, 아래에서 시작하는 다른 길과 겹치지않으려면 제일 좋은 이동은 오른쪽 대각선 위 > 오른쪽 > 오른쪽 대각선 아래 가 된다. 우리는 그냥 길을 최대한 많이 만들면 되므로 Greedy 하게 위에서부터 길을 만들어가면 된다.

 2. 일반적인 DFS 탐색은 탐색하고 돌아오면 visited 배열을 다시 초기화해주나, 이 문제에서는 방문해서 끝에 도달하지 못한 경로는 다른 지점에서 방문해도 어차피 끝에 도달하지 못하므로 visited 배열을 다시 초기화 해줄 필요가 없고, 초기화해준다면 경로가 겹쳐서 시간초과에 직면하게 됩니다.

알고리즘

 왼쪽 위에서부터 시작해서 DFS 탐색을 진행한다. 우선 순위가 높은 오른쪽 대각선 위, 오른쪽, 오른쪽 대각선 아래 순으로 탐색을 진행하고 끝에 도달하게 된다면 S 지점에서 끝에 도달했다고 checked 를 한다. 그 후, S 지점에서 시작하던 탐색들을 다 종료한다. 재귀적으로 탐색을 하고 돌아왔다는 사실은 그 길로는 오른쪽 끝에 도달하지 못한다는 것을 의미한다. 따라서, visited 배열을 초기화하지 않고 그대로 남겨두어 다른 경로에서 이 지점을 탐색하는 불필요한 연산을 줄여주면 시간초과를 피할 수 있다.

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
#include<iostream>
 
using namespace std;
 
int R, C, ans;
int dx[3= { -101 }, dy[3= { 111 };
bool checked[10000];
bool visited[10000][500];
char map[10000][501];
 
void DFS(int s, int x, int y) {
    if (y == C - 1) {
        ans++;
        checked[s] = 1;
        return;
    }
    
    for (int i = 0; i < 3; i++) {
        int nX = x + dx[i], nY = y + dy[i];
        if (!(0 <= nX && nX < R && 0 <= nY && nY < C)) continue;
        if (visited[nX][nY]) continue;
        if (map[nX][nY] == 'x'continue;
 
        visited[nX][nY] = 1;
        DFS(s, nX, nY);
        if (checked[s]) return;
    }
}
 
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++){    
        visited[i][0= 1;
        DFS(i, i, 0);
    }
    cout << ans << '\n';
    return 0;
}
cs

 

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

[BOJ] 15906 : 변신 이동 게임  (0) 2020.07.06
[BOJ] 18809 : Gaaaaaaaaaarden  (0) 2020.07.02
[BOJ] 15681 : 트리와 쿼리  (0) 2020.07.01
[BOJ] 18235 : 지금 만나러 갑니다  (0) 2020.06.25
[BOJ]15591 : MooTube(Silver)  (0) 2020.06.22

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


문제해석

 루트가 정해진 트리가 주어졌을 때, 임의의 노드의 SubTree의 크기를 구하자.

 이때, 트리가 이진트리! 라는 말이 없으므로 조심해야한다.

문제핵심

 이 문제의 핵심은 SubTree의 크기를 구해야되는 쿼리가 10^5번 들어올 수 있기 때문에, 처음에 Root부터 시작해서 모든 노드의 SubTree의 크기를 구해둬야한다. ( 그렇게 안해도 시간 초과가 안나오는지는 모르겠습니다! ) 쿼리마다 구하면 아마 시간초과가 나올 것 같다.

알고리즘

 TreeDp를 이용해서 Root 부터 시작해서 재귀적으로 모든 노드들의 SubTree 의 크기를 구해야한다. 현재 노드가 연결된 노드들이 이미 다 방문한 노드라면, 해당 노드는 leaf 노드 이므로 Dp 값은 0이고, 1을 return 해주면 된다. leaf 노드가 아니라면 자식 노드들의 subTree 값을 모두 더해서 저장하고, 자기 자신을 포함해서 subTree + 1 값을 return 해주면 된다.

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
#include<iostream>
#include<vector>
using namespace std;
int subtree[100001];
vector<int> tree[100001];
bool visited[100001];
int countSubtree(int cur) {
    // 연결된 애들 중에 더 이상 체크할게 없을 때 --> leaf 노드일 때 1
    bool check = false;
    int sum = 0;
    for (int i = 0; i < tree[cur].size(); i++) {
        int next = tree[cur][i];
        if (!visited[next]) {
            visited[next] = 1;
            sum += countSubtree(next);
            check = true;
        }
    }
    if (!check) return 1;
    subtree[cur] = sum;
    return subtree[cur] + 1;
}
int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int N, R, Q, u, v;
    cin >> N >> R >> Q;
    for (int i = 0; i < N - 1; i++) {
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    visited[R] = 1;
    countSubtree(R);
    for (int i = 0, x; i < Q; i++) {
        cin >> x;
        cout << subtree[x] + 1 << '\n';
    }
    return 0;
}
cs

 

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

[BOJ] 18809 : Gaaaaaaaaaarden  (0) 2020.07.02
[BOJ] 3109 : PLINOVOD  (0) 2020.07.01
[BOJ] 18235 : 지금 만나러 갑니다  (0) 2020.06.25
[BOJ]15591 : MooTube(Silver)  (0) 2020.06.22
[BOJ] 10021 : Watering the Fields  (0) 2020.06.22

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


문제해석

 1 ~ N 까지 좌표들이 있고, 오리 두마리가 A 와 B 지점에서 시작해서 하루에 한 번 점프를 해가며 이동한다. 점프를 안하는 경우는 없고,  점프 크기는 1부터 시작해서 2배씩 늘어난다. 이때, 둘이 만날 수 있는지, 만난다면 몇 일이 지나고 만나는지를 구하는 프로그램을 작성하라.

문제핵심

 1. 점프 크기가 2배씩 늘어나므로 19일에 점프크기는 262,144(2^18)가 되고 N이 최대 500,000이므로 20일부터는 의미가 없어진다. 왜냐하면 20일 부터 점프 크기가 500,000보다 크므로 점프를 하면 어차피 범위를 벗어나게 된다. 따라서, 우리는 19일까지만 시뮬레이션을 진행해보면 된다. --> 19 * O(N) 으로 처리하자.

 2. 우리는 k 일에 A 오리와 B 오리가 갈 수 있는 모든 지점을 알고 있다면, (k + 1) 일에 A 오리와 B 오리가 갈 수 있는 모든 지점을 구할 수 있다. --> 모든 지점을 저장해가면서 진행해보자.

알고리즘

 문제핵심 1, 2번에 의해서 현재 A, B 오리가 도착한 지점을 통해 다음 A, B 오리가 도착한 지점을 저장해나가며 문제를 해결할 수 있다. 메모리를 조금이라도 아끼기위해 bool 배열과 flag 기법을 이용해서 저장해나갔다.

 day 0부터 시작해서 day가 짝수라면 visited[0] 에 현재 방문한 지점들이 있고, visited[1] 에 다음에 방문할 지점들을 check 해가며 문제를 해결했다.

 1) 현재 방문한 지점들 중에 겹치는 곳이 있는지. --> 있다면 오리가 만났다!

 2) 다음에 방문한 지점을 저장할 공간을 초기화한다.

 3) 현재 지점을 토대로 다음에 방문할 지점을 계산해 저장한다.

알고리즘회고

처음에는 2개의 deque를 이용해서 현재 A 오리가 갈 수 있는 모든 지점을 저장하고, B 오리가 갈 수 있는 모든 지점을 저장하고, deque의 앞에서부터 현재 지점들을 뽑아가며 jump 를 +, jump 를 - 해서 다음 지점들을 다시 deque의 뒤로 push 했다. 당연히 deque의 push, pop 이 굉장히 많이 일어났고, 현재 지점들에서 같은 지점이 있는지 없는지 체크를 위해 2개의 deque을 2중 for문으로 순회해야되서 시간초과를 맛보게 되었다. 따라서, 메모리를 조금 더 사용하고 deque을 사용하지 않는 방식으로 알고리즘을 갈아엎었다.

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
#include<iostream>
#include<qcueue>
#include<vector>
 
using namespace std;
int N, A, B, jump = 1, day = 0;
bool visitedA[2][500001= {};
bool visitedB[2][500001= {};
int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
 
    // 19일 안에 만나야됨!
 
    cin >> N >> A >> B;
 
    visitedA[0][A] = 1;
    visitedB[0][B] = 1;
    do {
        // day 가 짝수라면 visited[0] 에 기존것이있음!
        // day 가 홀수라면 visited[1] 에 기존것이있음!
        int today = day % 2;
        int nextDay = (today + 1) % 2;
        // 같은게 있는지 찾아보자.
        bool flag = false;
        for (int i = 1; i <= N; i++) {
            if (visitedA[today][i] && visitedB[today][i]) {
                flag = true;
                break;
            }
        }
        if (flag) {
            cout << day << '\n';
            break;
        }
 
        // 초기화 진행
        for (int i = 1; i <= N; i++){
            visitedA[nextDay][i] = 0;
            visitedB[nextDay][i] = 0;
        }
        for (int i = 1; i <= N; i++) {
            int nextJump1 = i + jump, nextJump2 = i - jump;
            if (visitedA[today][i]) {
                if (1 <= nextJump1 && nextJump1 <= N) visitedA[nextDay][nextJump1] = 1;
                if (1 <= nextJump2 && nextJump2 <= N) visitedA[nextDay][nextJump2] = 1;
            }
            if (visitedB[today][i]) {
                if (1 <= nextJump1 && nextJump1 <= N) visitedB[nextDay][nextJump1] = 1;
                if (1 <= nextJump2 && nextJump2 <= N) visitedB[nextDay][nextJump2] = 1;
            }
        }
        jump *= 2;
        day++;
    } while (jump <= 500000);
    if (jump > 500000cout << -1;
    return 0;
}
cs

 

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

[BOJ] 3109 : PLINOVOD  (0) 2020.07.01
[BOJ] 15681 : 트리와 쿼리  (0) 2020.07.01
[BOJ]15591 : MooTube(Silver)  (0) 2020.06.22
[BOJ] 10021 : Watering the Fields  (0) 2020.06.22
[BOJ] 2352 : 반도체 설계  (0) 2020.06.15

+ Recent posts