문제

전쟁에 참여한 화랑이는 적군의 기지에 침투하여 정보를 훔쳐오는 임무를 받았습니다. 화랑이는 야간 전술 보행을 이용하여 직진하며, 야간 전술 보행은 1m/s의 일정한 속도로 나아갈 수 있습니다. 화랑이의 침입 경로에는 경비병들이 각자 일부 구간들을 감시하고 있습니다. 각각의 경비병들이 감시하는 구간은 서로 겹치지 않으며, 일정 시간 동안 근무 후 일정 시간 동안 휴식을 취하는 과정을 반복합니다. 화랑이가 지나가고 있는 위치를 감시하는 경비병이 근무 중이라면 화랑이는 경비병에게 발각되어 즉시 붙잡히게 됩니다. 하지만 해당 위치를 감시하는 경비병이 휴식을 취하고 있으면 화랑이는 무사히 해당 위치를 지나갈 수 있습니다. 경비병의 근무 정보를 모르는 화랑이는 쉬지 않고 전진을 하며, 화랑이가 출발할 때 모든 경비병들이 동시에 근무를 시작합니다.

 

예를 들어, 적군 기지까지의 거리가 10m이고 2명의 경비병들이 근무를 시작한다고 가정합시다. 첫 번째 경비병의 감시 구간은 화랑이의 출발 위치로부터 3m부터 4m까지이고, 두 번째 경비병의 감시 구간은 화랑이의 출발 위치로부터 5m부터 8m까지입니다. 첫 번째 경비병의 근무 및 휴식 시간은 각각 2초, 5초를 반복하며, 두 번째 경비병의 근무 및 휴식 시간은 각각 4초, 3초를 반복합니다. 이 경우 출발지로부터 화랑이의 위치에 따른 두 경비병의 상태는 아래 표와 같습니다. 첫 번째 경비병이 감시하는 3m ~ 4m 구간을 화랑이는 3초일 때 진입하지만, 첫 번째 경비병은 2초간의 근무를 마치고, 휴식을 시작했으므로, 화랑이는 붙잡히지 않습니다. 하지만 두 번째 경비병이 감시하는 5m ~ 8m 구간에서 화랑이가 8m 지점에 진입했을 때, 두 번째 경비병이 3초간의 휴식을 마치고 근무를 시작하므로 화랑이는 붙잡히게 됩니다.

 

화랑이의 현재 위치와 적군 기지 사이의 거리를 나타내는 정수 distance, 각 경비병의 감시 구간을 담은 2차원 정수 배열 scope, 같은 순서로 각 경비병의 근무 시간과 휴식 시간을 담은 2차원 정수 배열 times가 주어질 때, 화랑이가 경비를 피해 최대로 이동할 수 있는 거리를 return 하는 solution 함수를 완성하세요.


입력

  • 10 ≤ distance ≤ 10,000,000
  • 1 ≤ scope의 길이, times의 길이 ≤ 1,000
  • scope[i]는 i+1번째 경비병이 감시하는 구간입니다.
    • scope[i]를 [a, b]라고 했을 때, (a ≠ b)입니다.
    • scope[i]는 정렬되어 있지 않을 수 있습니다(예시 2번 참조).
    • 경비병의 감시구간은 서로 겹치지 않습니다.
    • 1 ≤ scope의 원소 ≤ distance
  • times[i]는 i+1번째 경비병의 [근무 시간, 휴식 시간]입니다.
    • 1 ≤ times의 원소 ≤ 10
  • 화랑이는 정수 위치에서만 붙잘힐 수 있습니다.
  • 모든 경비병들은 0초에 휴식 상태이며 1초부터 근무를 시작합니다.
  • 근무와 휴식을 전환하는 순간의 상태는 전환된 상태를 따릅니다.
    • 본문의 표에서 3초 일 때, 첫 번째 경비병은 근무에서 휴식으로 전환했기 때문에 휴식 상태입니다.
    • 또한, 8초 일 때, 두 번째 경비병은 휴식에서 근무로 전환했기 때문에 근무 상태입니다.

바로 예시와 함께 문제 풀이 시 고려해야 할 사항들을 살펴보자.

 

distanace = 12, scope = { {7, 8}, {4, 6}, {10, 11} }, times = { {2, 2}, {2, 4}, {3, 3} }

 

1 2 3 4 5 6 7 8 9 10 11 12

1.scope  & times 

일단, 가장 먼저 scope를 오름차순으로 정렬하여 빠르게 도달할 수 있는 경비 구역 순(4~6, 7~8, 10~11)으로 확인하고자 했다. 

하지만, scope를 단순하게 정렬하면 scope와 times의 인덱스가 일치하지 않게 되는 문제가 있었다.

무슨 말이냐면, 가장 먼저 살피게 될 경비 구역은 4~6(idx = 1 -> 0)인데,

오름차순으로 정렬하면서 기존 인덱스 1에서 0으로 변경되고 이로 인해 몇 번째 times을 고려해야 하는 지 알 수 없게 되는 것이다.

따라서, 기존 idx를 유지하면서 정렬 상태를 만들기 위해 map 자료구조를 사용하였다.

(map은 key 값을 기준으로 오름차순 정렬되기 때문에 선택함. 따라서, map대신 priority_queue를 사용해도 무방함.)

아래와 같이 map의 key값을 scope 구역으로 value를 idx로 저장하는 것이다.

map[{left, right}] = idx;

 

2.근무 & 휴식

위와 같이 빠르게 도달할 수 있는 순서로 경비 구역 scope를 정렬했다면,

이제 해당 경비 구역에 도달했을 시, 경비병의 근무 및 휴식 상태를 확인해야 한다. 

위 예제를 통해 순서대로 살펴보자.

times = { {2, 2}, {2, 4}, {3, 3} }

o o o  x x x o o o  x   x   x

o o x  x o o x x o  o   x   x

o o x  x x x o o x  x   x   x

1 2 3 4 5 6 7 8 9 10 11 12

(o : 근무 중 , x : 휴식 중)

 

4~6 구역과 달리 7~8 구역은 해당 경비병이 이미 한번의 근무와 휴식을 두 번째 근무와 휴식을 반복하고 있는 상태이다.

즉, 현재 시간(=distance)을 기준으로 해당 구역 경비병의 반복되는 근무/휴식 상태를 판단해야 한다.

 

반복되는 근무/휴식을 배열을 통해 아래와 같이 표현해보자.

times = { {2, 2}, {2, 4}, {3, 3} }

1. [o, o, x, x, x, x] -> mod(2+4 = 6)

     1, 2, 3, 4, 5, 6

     7, 8, 9,10,11,12

2. [o, o, x ,x] -> mod(2+ 2 = 4)

     1, 2, 3, 4

     5, 6, 7, 8

3. [o, o, o, x, x, x] -> mod(3 + 3 = 6)

i = 0, 1, 2, 3, 4, 5 (=인덱스)

s =1, 2, 3, 4, 5, 6, 7, 8, 9 ,10, 11, 12 ... (=시간)

 

현재 시간 s에 -1 해준 값을 times[i][0] + times[i][1] 값으로 모듈러 연산을 해준 값이 times[i][0] 보다 작다면 근무 상태인 것을 알 수 있다. 이런 순환형태에서 모듈러 연산을 사용한다면 문제를 쉽게 해결 할 수 있을 것이다.

 

#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;

map<pair<int, int>, int> sorted;

int solution(int distance, vector<vector<int>> scope, vector<vector<int>> times) {
    int answer = 0, catched = 0;
    for(int i = 0; i < scope.size(); i++){
    		//scope[i]는 정렬되어 있지 않을 수 있다고 했으므로
            //항상 scope[0] < scope[1]이 만족되지 않음. 
        int l = min(scope[i][0], scope[i][1]);
        int r = max(scope[i][0], scope[i][1]);
        sorted[{l, r}] = i; //경비 구역을 오름차순으로 정렬하기 위한 map 자료구조
    }
    for (auto iter = sorted.begin(); iter != sorted.end(); iter++) {
        if(catched) break; //경비병에서 잡혔다면 더이상 진행x
        int s = iter->first.first;
        int e = iter->first.second;
        int idx = iter->second;
        int mod = times[idx][0] + times[idx][1];
        for(int pos = s; pos <= e; pos++){
            answer = pos;
            if((pos-1) % mod < times[idx][0]){
                catched = 1;
                break;
            }
        }
    }
    if(!catched) return distance; //경비병에게 잡히지 않았다면 끝(=distance)까지 도달한 것. 
    return answer;
}

 

level2 치고 문제가 너무 어려운게 아닌가 생각했는데, 다른 사람들의 풀이를 보면서 내가 문제를 어렵게 접근했다는 것을 알 수 있었다. 굳이 scope를 빠르게 도달하는 순으로 정렬할 필요 없이 scope를 끝까지 확인하면서 경비병에게 걸리지 않는 가장 왼쪽 위치를 찾으면 쉽게 좀 더 쉽게 해결할 수 있는 문제였다 ㅎㅎ;;

 

//출처 : https://husk321.tistory.com/395
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int solution(int distance, vector<vector<int>> scope, vector<vector<int>> times) {
    int answer = distance;
    
    for(int i = 0; i < scope.size(); i++){
        int fromDist = min(scope[i][0], scope[i][1]);
        int toDist = max(scope[i][0], scope[i][1]);
        
        int workTime = times[i][0];
        int restTime = times[i][1];
        
        for(int pos = fromDist; pos <= toDist; pos++){
            if(pos % (workTime + restTime) == 0){
                continue;
            }
            if(pos % (workTime + restTime) <= workTime){
                answer = min(answer, pos);
            }
        }        
    }
        
    return answer;
}