문제 설명


단체사진 찍기

가을을 맞아 카카오프렌즈는 단체로 소풍을 떠났다. 즐거운 시간을 보내고 마지막에 단체사진을 찍기 위해 카메라 앞에 일렬로 나란히 섰다. 그런데 각자가 원하는 배치가 모두 달라 어떤 순서로 설지 정하는데 시간이 오래 걸렸다. 네오는 프로도와 나란히 서기를 원했고, 튜브가 뿜은 불을 맞은 적이 있던 라이언은 튜브에게서 적어도 세 칸 이상 떨어져서 서기를 원했다. 사진을 찍고 나서 돌아오는 길에, 무지는 모두가 원하는 조건을 만족하면서도 다르게 서는 방법이 있지 않았을까 생각해보게 되었다. 각 프렌즈가 원하는 조건을 입력으로 받았을 때 모든 조건을 만족할 수 있도록 서는 경우의 수를 계산하는 프로그램을 작성해보자.

입력 형식

입력은 조건의 개수를 나타내는 정수 n과 n개의 원소로 구성된 문자열 배열 data로 주어진다. data의 원소는 각 프렌즈가 원하는 조건이 N~F=0과 같은 형태의 문자열로 구성되어 있다. 제한조건은 아래와 같다.

  • 1 <= n <= 100
  • data의 원소는 다섯 글자로 구성된 문자열이다. 각 원소의 조건은 다음과 같다.
    • 첫 번째 글자와 세 번째 글자는 다음 8개 중 하나이다. {A, C, F, J, M, N, R, T} 각각 어피치, 콘, 프로도, 제이지, 무지, 네오, 라이언, 튜브를 의미한다. 첫 번째 글자는 조건을 제시한 프렌즈, 세 번째 글자는 상대방이다. 첫 번째 글자와 세 번째 글자는 항상 다르다.
    • 두 번째 글자는 항상 ~이다.
    • 네 번째 글자는 다음 3개 중 하나이다. {=, <, >} 각각 같음, 미만, 초과를 의미한다.
    • 다섯 번째 글자는 0 이상 6 이하의 정수의 문자형이며, 조건에 제시되는 간격을 의미한다. 이때 간격은 두 프렌즈 사이에 있는 다른 프렌즈의 수이다.

출력 형식

모든 조건을 만족하는 경우의 수를 리턴한다.

예제 입출력

ndataanswer

2 ["N~F=0", "R~T>2"] 3648
2 ["M~C<2", "C~M>1"] 0

예제에 대한 설명

첫 번째 예제는 문제에 설명된 바와 같이, 네오는 프로도와의 간격이 0이기를 원하고 라이언은 튜브와의 간격이 2보다 크기를 원하는 상황이다.

두 번째 예제는 무지가 콘과의 간격이 2보다 작기를 원하고, 반대로 콘은 무지와의 간격이 1보다 크기를 원하는 상황이다. 이는 동시에 만족할 수 없는 조건이므로 경우의 수는 0이다.

 

 

 

접근 방법


백트래킹 기법을 사용하여 해결할 수 있었습니다.

 

1. 백트래킹을 사용하여 8명이 서있을 수 있는 모든 경우의 수에 대한 순열을 구합니다. (이 문제의 경우 8!개)

 

2. 구해진 한 조합에 대해 각각의 프렌즈 들이 원하는 조건(data)가 전부 만족하는지 확인합니다.

 

3. 한 조건에 대해 검사를 하는 방법은 아래와 같습니다.

 

3-1. 조건의 0번째 인덱스와 2번째 인덱스인 두 명과, 3번째 인덱스인 오퍼레이션(<, >, =) 마지막으로 4번째 인덱스인  오퍼레이션에 대한 수를 parsing합니다.

 

3-2. 아래와 같은 코드로 각 조건이 만족하는지 검사합니다.

if(operation == '='){
	if(Math.abs(first - second) == num + 1)
		return true;
}
else if(operation == '>'){
	if(Math.abs(first-second) > num + 1)
		return true;
}
else{
	if(Math.abs(first-second) < num + 1)
		return true;
}
return false;

 

 

소스 코드


import java.util.*;
import java.lang.*;

class Solution {
    
    boolean visited[] = new boolean[8];
    String friends = "ACFJMNRT";
    String batch = "";
    String[] data;
    int ans = 0;
    
    public boolean checkOne(String condition){
        
        // 2명의 위치, 연산자, 숫자
        int first = batch.indexOf(condition.substring(0, 1)); 
        int second = batch.indexOf(condition.substring(2, 3));
        char operation = condition.charAt(3);
        Integer num = Integer.parseInt(condition.substring(condition.length() - 1));
        
        if(operation == '='){
            if(Math.abs(first - second) == num + 1)
                return true;
        }
        else if(operation == '>'){
            if(Math.abs(first-second) > num + 1)
                return true;
        }
        else{
            if(Math.abs(first-second) < num + 1)
                return true;
        }
        return false;
    }
    
    //전체 데이터에 대한 검사
    public boolean check(){
        
        for(int i=0;i<data.length;i++){
            if(!checkOne(data[i])) //하나라도 만족하지 않는다면
                return false;
        }
        return true;
    } 
    
    public void backTracking(int cnt){
        
        //8명의 순열이 완성되었을 때
        if(cnt == 8){
            if(check()) //전체 조건을 검사
                ans++;  //만족한다면 ans++
            return;
        }
        
        for(int i=0;i<8;i++){
            if(visited[i] == false){
                visited[i] = true;
                batch += friends.substring(i,i+1);
                backTracking(cnt + 1);
                batch = batch.substring(0, batch.length() - 1);
                visited[i] = false;
            }    
        }
    }
    
    public int solve(int n, String[] data){
        
        this.data = data;
        backTracking(0);
        return ans;
    }
    
    public int solution(int n, String[] data) {
        int answer = 0;
        return answer = solve(n, data);
    }
}

문제 설명


보행자 천국

카카오내비 개발자인 제이지는 시내 중심가의 경로 탐색 알고리즘 개발 업무를 담당하고 있다. 최근 들어 보행자가 자유롭고 편리하게 걸을 수 있도록 보행자 중심의 교통 체계가 도입되면서 도심의 일부 구역은 자동차 통행이 금지되고, 일부 교차로에서는 보행자 안전을 위해 좌회전이나 우회전이 금지되기도 했다. 복잡해진 도로 환경으로 인해 기존의 경로 탐색 알고리즘을 보완해야 할 필요가 생겼다.

도시 중심가의 지도는 m × n 크기의 격자 모양 배열 city_map으로 주어진다. 자동차는 오른쪽 또는 아래 방향으로 한 칸씩 이동 가능하다.

city_map[i][j]에는 도로의 상황을 나타내는 값이 저장되어 있다.

  • 0인 경우에는 자동차가 자유롭게 지나갈 수 있다.
  • 1인 경우에는 자동차 통행이 금지되어 지나갈 수 없다.
  • 2인 경우는 보행자 안전을 위해 좌회전이나 우회전이 금지된다. (왼쪽에서 오던 차는 오른쪽으로만, 위에서 오던 차는 아래쪽으로만 진행 가능하다)

도시의 도로 상태가 입력으로 주어졌을 때, 왼쪽 위의 출발점에서 오른쪽 아래 도착점까지 자동차로 이동 가능한 전체 가능한 경로 수를 출력하는 프로그램을 작성하라. 이때 가능한 경로의 수는 컴퓨터가 표현할 수 있는 정수의 범위를 넘어설 수 있으므로, 가능한 경로 수를 20170805로 나눈 나머지 값을 출력하라.

입력 형식

입력은 도시의 크기를 나타내는 m과 n, 그리고 지도를 나타내는 2차원 배열 city_map으로 주어진다. 제한조건은 아래와 같다.

  • 1 <= m, n <= 500
  • city_map의 크기는 m × n이다.
  • 배열의 모든 원소의 값은 0, 1, 2 중 하나이다.
  • 출발점의 좌표는 (0, 0), 도착점의 좌표는 (m - 1, n - 1)이다.
  • 출발점과 도착점의 city_map[i][j] 값은 0이다.

출력 형식

출발점에서 도착점까지 이동 가능한 전체 경로의 수를 20170805로 나눈 나머지를 리턴한다.

예제 입출력

mncity_mapanswer

3 3 [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 6
3 6 [[0, 2, 0, 0, 0, 2], [0, 0, 2, 0, 1, 0], [1, 0, 0, 2, 2, 0]] 2

예제에 대한 설명

첫 번째 예제는 모든 도로가 제한 없이 통행 가능한 경우로, 가능한 경우의 수는 6가지이다.
두 번째 예제는 문제 설명에 있는 그림의 경우이다. 가능한 경우의 수는 빨간 실선과 노란 점선 2가지뿐이다.

 

 

접근 방법


 동적계획법으로 해결할 수 있는 문제였습니다. 동적계획법은 dp[x좌표][y좌표][현재방향]으로 설계할 수 있으며 현재방향에 대한 차원이 필요한 이유는 만약 어떤 지점의 값이 2일때 한 방향으로만 갈 수 있으므로 현재 어떤 방향을 가지고 있는가에 따라 다른 경우의 수가 되기 때문입니다. 

 

 자세한 내용은 코드에 주석을 달아놓겠습니다.

 

소스 코드


#include <vector>
#include <string.h>

using namespace std;

int MOD = 20170805;
int dp[501][501][2]; //마지막 차원은 현재 방향
pair<int, int> nextdir[2] = {{1,0},{0,1}}; //오른쪽 또는 아래로만 이동 가능
vector<vector<int>> map;
int M, N;

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

int solve(int x, int y, int dir){
    
    int &ref = dp[x][y][dir];
    
    //방문한 적 있다면
    if(ref != -1)
        return ref;
    //목표지점에 도달했다면
    if(x == M - 1 && y == N - 1)
        return ref = 1;
    
    //방문 표시
    ref = 0;
    //2방향 모두 가능
    if(map[x][y] != 2){
        
        //2방향에 대한 반복문
        for(int i=0;i<2;i++){
            int next_x = x + nextdir[i].first;
            int next_y = y + nextdir[i].second;
            //다음 좌표가 범위를 벗어나지 않으며 통행 불가 지역이 아닌 경우
            if(inRange(next_x, next_y) && map[next_x][next_y] != 1)
                ref += solve(next_x, next_y, i) % MOD;     
        }    
    }
    else{ //회전 불가능, map[x][y] == 2인 경우
        //현재 진행 방향이 dir이며 dir로만 진행 가능
        int next_x = x + nextdir[dir].first;
        int next_y = y + nextdir[dir].second;
        if(inRange(next_x, next_y) && map[next_x][next_y] != 1)
            ref += solve(next_x, next_y, dir) % MOD;     
    }
    return ref %= MOD;
}

// 전역 변수를 정의할 경우 함수 내에 초기화 코드를 꼭 작성해주세요.
int solution(int m, int n, vector<vector<int>> city_map) {
    
    int answer = 0;
    memset(dp, -1, sizeof(dp));
    //전역 변수로 초기화
    M = m; N = n; map = city_map;
    
    return answer = solve(0, 0, 0);
}

+ Recent posts