호우동의 개발일지

Today :

article thumbnail

문제 이해 단계

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

 

12851번: 숨바꼭질 2

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

www.acmicpc.net

X좌표로만 이루어진 맵이 존재한다. 
출발점 N과 도착점 K가 입력으로 들어온다.

출발점 N에서 할 수 있는 움직임은 총 3개이다.

1. X+1
2. X-1
3. X*2

3개의 움직임을 적절히 조합해서 도착점 K까지 갈 수 있는 가장 빠른 시간을 구하고,
가장 빠른 시간으로 갈 수 있는 경우의 수를 출력하는 문제

 


문제 접근 단계


숨바꼭질 시리즈 문제이다.

일단 제한조건부터 살펴보면
좌표는 최대 100,000까지 가능하다.

만약 O(N^2)으로 문제를 푼다면
10^10으로 시간초과가 뜬다는 것을 유의하자.

 


유형 분석

일단 이 문제는 어떤 유형의 문제일까?

찾아야 하는 것은 출발지에서 도착점까지의 가장 빠른 시간과 그때의 경우의 수이다.

여기서 결국 시간은 거리라고 볼 수 있기 때문에
최단경로라고 봐도 무방하다.

움직임이 정해져 있고, 맵에서 하나의 목표물을 최단거리로 목적지까지 움직이는 것이다.
그래서 이 문제는 일단 BFS(우선너비탐색)을 사용하는 것이 낫다는 판단을 했다.

일반적인 BFS를 사용하면 최단거리는 쉽게 구할 수 있다.
문제는 두 번째로 구해야 할 것(경우의 수)이다. 이걸 어떻게 구하지?

BFS의 원리를 생각하면서 그림을 그려보자.

그림1

2번에서 시작해서 BFS를 한번 돌리면 이런 식으로 갈 것이다.

그림2

BFS를 두 번 돌리면 목적지인 6에 하나가 도달하는 것을 볼 수 있다.
이때 최소 횟수가 2회인 것을 확인할 수 있다.

이제 나머지를 확인할 필요가 있을까? 필요가 없다.
왜냐하면 최소 횟수 2회를 넘어선 것이기 때문에 포함시키지 않아도 된다.

즉, 최소 시간일 때 목적지에 도달한 움직임만이
경우의 수에 포함된다는 것이다.

이를 이용하여 BFS를 구성하면 된다.

자세한 것은 문제 구현단계에서 보도록 하자.

 


문제 구현 단계

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

bool visited[100001] = {false,};

int N, K;
int short_time;
int kind = 0;
int dx[] = {-1,1};
void bfs(int sx){
    queue<pair<int,int>> q;
    visited[sx] = true;
    q.push({sx,0}); // 큐 안에 (좌표,시간)쌍으로 넣음

    while(!q.empty()){
        int x = q.front().first;
        int time = q.front().second;
        q.pop();
        // 현재 좌표가 목적지 && 첫 방문일 때
        if(x == K && !visited[x]){
            short_time = time; // 최소시간으로 지정
            kind++; // 방문횟수 +1
        }
        // 현재 좌표가 목적지 && 현재 시간이 최소 시간일 때
        else if(x == K && time == short_time) kind++;
        visited[x] = true; // 방문처리


        for(int i = 0; i < 2; i++){
            int nx = x + dx[i];
            if(nx > 100000 || nx < 0 || visited[nx]) continue;
            q.push({nx,time+1}); // 큐에 (다음좌표,현재시간+1)로 넣어줌
        }
        //마찬가지
        if(2*x <= 100000 && !visited[2*x]) q.push({2*x,time+1});
    }
}

int main(){
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    cin >> N >> K;

    bfs(N);
    cout << short_time << "\n" << kind;
}

집중해서 보아야 하는 것은 bfs함수 쪽이다.
큐를 만들어줄 때 pair를 사용하여 (좌표, 시간) 쌍으로 만들어줬다.

이렇게 함으로써 특정 좌표일 때의 시간을 알 수 있도록 만들어줬다.

bfs의 실행은 간단하다.
우선 항상 해당 좌표가 목적지인지 확인해 준다.

그리고 그 좌표가 목적지가 맞다면
그다음 체크해 주는 것은 첫 방문인가 아닌가이다.

만약 첫 방문이라면 그때의 시간을 최소시간으로 지정해 준다.
그리고 횟수+1을 해준다.

만약 첫 방문이 아니라면, 그때의 시간이 최소시간인지 확인한다.
최소시간이 맞다면 횟수+1을 해준다. 그 외에는 일반적인 BFS와 동일하다.

다른 것은 큐 안에 pair을 넣어줬기 때문에
(다음좌표, 현재시간+1) 이런 식으로 넣어줘야 한다는 것이다.

그렇게 bfs를 완료하면, 전역변수로 설정해 둔 short_time과 kind를 출력해 준다.

 


시행착오

최소시간을 구하는 것은 쉬웠다.
하지만 문제는 경우의 수를 구하는 것이었다.

경우의 수를 구할 때 사용했던 방식은
백트래킹, 너비우선 탐색을 사용한 함수 하나 더 만들기.. 등등이 있는데
시간 초과, 메모리 초과 등 다양한 실패를 맞았다.

일단 내가 잘못했던 점은 너비 우선 탐색의 원리를 제대로 파악하지 않은 점.
만약 제대로 파악했다면 이 문제를 풀었을 것이다.

좀 더 공부하자.

https://toss.me/howudong

 

토스아이디

 

toss.me