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

 

15686번: 치킨 배달

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸

www.acmicpc.net

문제

DFS + 구현 문제이다. 처음 시간 초과나서 방법을 좀 바꿔서 다시 풀었다.

 

해결 방안

처음에는 전체 집List, 전체 치킨집List와 visited 배열을 사용하여 전체 치킨집 중 M개를 DFS 와 백트래킹을 이용하여 구현하였다. 
하지만 거리를 구하는 과정에서 전체 집에서 전체 치킨집을 돌면서 M개에 포함되지 않는 경우도 조건을 통해 거르다 보니 시간 초과가 났다.

그래서 다음에는 visited 배열을 빼고 선택된 치킨집List를 만들어 M개 만큼 선택된 치킨집 List를 만들어서 거리를 구하였더니 맞출 수 있었다.

나머지 부분은 크게 어렵지 않은 부분이여서 생략한다.

 

코드 (시간 초과)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

// 치킨 배달
// NxN 도시
// 1x1 칸 (0:빈칸, 1:집, 2:치킨집)
// (r, c) r,c는 1부터 시작
// 치킨 거리 : 집과 가장 가까운 치킨집 사이의 거리
// 도시의 치킨 커리는 모든 집의 치킨 거리의 합
// 치킨집 갯수 M개

// M개 이외 치킨집 폐업, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램
public class Main {
	
	static int N; // 도시 크기 NxN(2~50)
	static int M; // 최대 치킨집 갯수(1~13)
	static int[][] map;

	static List<int[]> hList = new ArrayList<>(); // 전체 집 위치
	static List<int[]> cList = new ArrayList<>(); // 전체 치킨 집 위치
	static boolean[] visited;
	
	static int min = Integer.MAX_VALUE; // 가장 작은 도시의 치킨 거리
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str;
		str = br.readLine();
		
		N = Integer.parseInt(str.split(" ")[0]);
		M = Integer.parseInt(str.split(" ")[1]);
		
		map = new int[N+1][N+1];
		
		StringTokenizer st;
		for(int i=1; i<N+1; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			for(int j=1; j<N+1; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
				
				// 집 리스트
				if(map[i][j] == 1) {
					hList.add(new int[] {i, j});
				}
				// 치킨집 리스트
				else if(map[i][j] == 2) {
					cList.add(new int[] {i, j});
				}
			}
		}
		visited = new boolean[cList.size()];
		// ==== 입력 끝 =====
		
		dfs(0);
		
		System.out.println(min);

	}
	
	// 치킨집이 M만큼 있는 경우의 치킨 거리 탐색
	static void dfs(int depth) {
		// 치킨 집이 M만큼 있는 경우
		if(depth >= M) {
			int distanceSum = 0;
			
			int minDistance;
			int distance;
			// 각 집에서 M에 포함되는 치킨집들 중 가장 짧은 치킨 거리구하기
			for(int i=0; i<hList.size(); i++) {
				minDistance = Integer.MAX_VALUE;
				distance = 0;
				for(int j=0; j<cList.size(); j++) {
					// M에 포함된 치킨집
					if(visited[j]) {
						distance = Math.abs(hList.get(i)[0]-cList.get(j)[0]) + Math.abs(hList.get(i)[1]-cList.get(j)[1]);
						minDistance = Math.min(distance, minDistance);
					}
				}
				
				distanceSum += minDistance;
			}
			
			// 가장 작은 도시의 치킨 거리 구하기
			min = Math.min(min, distanceSum);
		}
		else {
			// M과 치킨집 갯수가 같으면 폐업되는 치킨 집 없음
			if(M == cList.size()) {
				for(int i=0; i<cList.size(); i++) {
					visited[i] = true;
				}
				dfs(M);
			}
			// list에서 치킨집 M만큼 고르기
			else {
				for(int i=0; i<cList.size(); i++) {
					if(!visited[i]) {
						visited[i] = true;
						dfs(depth+1);
						visited[i] = false;
					}
				}
			}
		}
	}

}

 

코드 (합격)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

// 치킨 배달
// NxN 도시
// 1x1 칸 (0:빈칸, 1:집, 2:치킨집)
// (r, c) r,c는 1부터 시작
// 치킨 거리 : 집과 가장 가까운 치킨집 사이의 거리
// 도시의 치킨 커리는 모든 집의 치킨 거리의 합
// 치킨집 갯수 M개

// M개 이외 치킨집 폐업, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램
public class Main {
	
	static int N; // 도시 크기 NxN(2~50)
	static int M; // 최대 치킨집 갯수(1~13)
	static int[][] map;

	static List<int[]> hList = new ArrayList<>(); // 전체 집 위치
	static List<int[]> cList = new ArrayList<>(); // 전체 치킨 집 위치
	static List<int[]> selected = new ArrayList<>(); // 선택된 치킨집
	
	static int min = Integer.MAX_VALUE; // 가장 작은 도시의 치킨 거리
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str;
		str = br.readLine();
		
		N = Integer.parseInt(str.split(" ")[0]);
		M = Integer.parseInt(str.split(" ")[1]);
	
		map = new int[N+1][N+1];
		
		StringTokenizer st;
		for(int i=1; i<N+1; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			for(int j=1; j<N+1; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
				
				// 집 리스트
				if(map[i][j] == 1) {
					hList.add(new int[] {i, j});
				}
				// 치킨집 리스트
				else if(map[i][j] == 2) {
					cList.add(new int[] {i, j});
				}
			}
		}
		// ==== 입력 끝 =====
		
		dfs(0,0);
		
		System.out.println(min);

	}
	
	// 치킨집이 M만큼 있는 경우의 치킨 거리 탐색
	static void dfs(int idx, int depth) {
		// 치킨 집을 M만큼 고른 경우
		if(depth >= M) {
			int distanceSum = 0;
			
			int minDistance;
			int distance;
			// 각 집에서 M에 포함되는 치킨집들 중 가장 짧은 치킨 거리구하기
			for(int i=0; i<hList.size(); i++) {
				minDistance = Integer.MAX_VALUE;
				distance = 0;
				for(int j=0; j<selected.size(); j++) {
					// M에 포함된 치킨집
					distance = Math.abs(hList.get(i)[0]-selected.get(j)[0]) + Math.abs(hList.get(i)[1]-selected.get(j)[1]);
					minDistance = Math.min(distance, minDistance);
					
				}
				distanceSum += minDistance;
			}
			
			// 가장 작은 도시의 치킨 거리 구하기
			min = Math.min(min, distanceSum);
		}
		else {
			
			// list에서 치킨집 M만큼 고르기
			for(int i=idx; i<cList.size(); i++) {
				selected.add(cList.get(i));
				dfs(i+1, depth+1);
				selected.remove(depth);
			}
			
		}
	}

}

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

 

15685번: 드래곤 커브

첫째 줄에 드래곤 커브의 개수 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 드래곤 커브의 정보가 주어진다. 드래곤 커브의 정보는 네 정수 x, y, d, g로 이루어져 있다. x와 y는 드래곤 커�

www.acmicpc.net

문제

구현 문제이다. 

 

해결 방안

map의 전체 크기는 101 x 101이 될 것이다. (전체 격자가 100x100 이므로)

1. 드래곤 커브의 갯수 N만큼 드래곤 커브의 정보들(x, y, d, g)을 받게 되는데

(drawDragonCurve() 함수 참고)
1.1. 각 드래곤 커브마다 g(세대)까지 map에 그려줘야한다.(드래곤 커브가 가지고 있는 꼭지점을 true로 만들어줘야한다. map[x][y] = true).
1.2. 이때 드래곤 커브를 그리는데 규칙이 있는데 다음 세대를 갈때 전 세대의 (가장 늦게 들어온 것부터 첫 번째 까지) 영향을 받는 것이다. 이때 반시계방향으로 방향이 바뀐다.
1.3. 따라서 드래곤 커브를 그리기 위해 해당 방향들을 list에 다 기억하고 있다가 주어진 x,y에 list 만큼 이어서 그려준다.
1.4. 드래곤 커브 갯수 N 만큼 드래곤 커브를 map에 그려준다. 

2. map에 드래곤 커브를 다 그리고 나면 1x1 정사각형의 네 꼭지점이 전부 true인 경우의 갯수를 찾으면 된다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

// 드래곤 커브
// 세가지 속성(시작점, 시작방향, 세대)
// 이차원 좌표 평면 위에서 정의됨(x축은 동 방향 y축은 남 방향)

// 첫째 줄에 크기가 1x1인 정사각형의 네 꼭짓점이 모두 드래곤 커브의 일부인 것의 갯수를 출력한다.
public class Main {
	
	// 전체 격자 100 x 100
	static int N; // 드래곤 커브 갯수 (1~20)
	static int x, y; // 드래곤 커브의 시작점(x,y) (0~100)
	static int d; // 시작 방향 (0~3) (0:동(x+)  1:북(y-)  2:서(x-)  3:남(y+))
	static int g; // 세대 (0~10)
	static boolean[][] map = new boolean[101][101]; // 드래곤 커브가 들어가면 해당 꼭지점(y, x)는 true
	
	static List<Integer> directions; // 선들의 방향을 저장할 List
	
	// (0:동(x+)  1:북(y-)  2:서(x-)  3:남(y+))
	static int[] dx = {1, 0, -1, 0};
	static int[] dy = {0, -1, 0, 1};
	
	public static void main(String[] args) throws NumberFormatException, IOException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		N = Integer.parseInt(br.readLine());
		
		String str;
		StringTokenizer st;
		for(int i=0; i<N; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			x = Integer.parseInt(st.nextToken());
			y = Integer.parseInt(st.nextToken());
			d = Integer.parseInt(st.nextToken());
			g = Integer.parseInt(st.nextToken());
			
			drawDragonCurve();
		}
		// === 입력 및 맵에 드래곤 커브 적용====
		
		int result = getSquareCnt();
		System.out.println(result);
	}
	
	// 드래곤 커브 그리기
	static void drawDragonCurve() {
		// 초기화
		directions = new ArrayList<>();
		directions.add(d); // 처음 방향 넣기

		// 드래곤 커브가 그려지는 선 방향 저장하기
		int dir;
		//g 세대 까지 반복
		while(g-- > 0) {
			// 전 세대의 영향을 받으며 stack과 같이 쌓여지므로 뒤에서부터 get
			for(int i=directions.size()-1; i>=0; i--) {
				dir = (directions.get(i)+1) % 4;
				directions.add(dir);
			}
		}
		
		// map에서 드래곤 커브가 지나는 꼭지점 그리기 
		int nx, ny;
		int cx = x;
		int cy = y;
		map[x][y] = true;
		for(int i=0; i<directions.size(); i++) {
			dir = directions.get(i);
			
			nx = cx + dx[dir];
			ny = cy + dy[dir];
			
			map[nx][ny] = true;
			
			// 계속 이어서 그려가는 것이므로 cx, cy를 바꿔줘야함
			cx = nx;
			cy = ny;
		}
	
	}
	
	//크기가 1x1인 정사각형의 네 꼭짓점이 모두 드래곤 커브의 일부인 것의 갯수를 구하는 함수
	static int getSquareCnt() {
		int cnt = 0;
		
		for(int i=0; i<100; i++) { //y축
			for(int j=0; j<100; j++) { //x축
				if(map[i][j] && map[i+1][j] && map[i][j+1] && map[i+1][j+1])
					cnt++;
			}
		}
		
		return cnt;
	}

}

 

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

 

15683번: 감시

스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감��

www.acmicpc.net

 

문제

DFS + 구현(Simultation) 문제이다.

해결 방안

CCTV 클래스를 만들어서 각 CCTV의 y(row), x(col), kinds(cctv 종류), dir[](해당 cctv가 가진 방향) 정보를 가지고 있도록 하였다.

1. Main에서 사무실 공간 값(map[i][j])을 입력 받을 때 cctv 값(1~5)가 포함되면 List<CCTV> cctvs에 넣어주었다.

2. cctvs 갯수(List 사이즈)만큼 DFS 탐색을 한다. 이때 DFS 탐색을 하는 목적은 List에 있는 CCTV들의 모든 방향을 설정해주기 위함이다.
이때 1,3,4 CCTV 같은 경우 4방향이 나올 수 있으므로 4가지 경우(90도씩 회전한 경우)를 DFS탐색을 해주고 2 CCTV 같은경우 2가지(상하, 좌우)의 경우를 DFS 탐색, 5 CCTV 같은 경우 1가지 경우를 DFS 탐색해준다.

3. CCTV 갯수만큼 DFS 탐색이 도달(기저사례)하게 되면 각 CCTV들이 감시하는 방향으로 copyMap을 재설정해준다. (문제의 예시에서는 #을 이용하였으나 여기서는 7값으로 해주었다.)

4. 그 후 copyMap에서 사각지대(0인 값) 인 부분을 세서 최소인 경우를 구해준다. 

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

// 감시
// NxM 직사각형 사무실
// 총 k개의 cctv
// cctv 종류 5개 
// cctv는 감시할 수 있는 방향에 있는 칸 전체를 감시할 수 있음
// 사무실에 벽이 있는데 cctv는 벽을 통과할 수 없다.
// cctv가 감시할 수 없는 영역은 사각지대
// cctv는 회전 시킬 수 있음(회전은 항상 90도 방향)
// 0은 빈칸 6은 벽 1~5는 cctv 번호

// 사각 지대의 최소 크기 출력
public class Main {
	
	static class CCTV{
		int y; // 세로
		int x; // 가로
		
		int kinds; // cctv 종류 (1~5)
		int[] dir; // 방향 (0~3)
		
		CCTV(int y, int x, int kinds){
			this.y = y;
			this.x = x;
			this.kinds = kinds;
			
			if(kinds==1) {
				this.dir = new int[1];
				dir[0] = 0;
			}
			else if(kinds==2) {
				this.dir = new int[2];
				dir[0] = 0;
				dir[1] = 2;
			}
			else if(kinds==3) {
				this.dir = new int[2];
				dir[0] = 0;
				dir[1] = 3;
			}
			else if(kinds==4) {
				this.dir = new int[3];
				dir[0] = 0;
				dir[1] = 2;
				dir[2] = 3;
			}
			else if(kinds==5) {
				this.dir = new int[4];
				dir[0] = 0;
				dir[1] = 1;
				dir[2] = 2;
				dir[3] = 3;
			}
		}
	}
	static int N; // 세로 (1~8)
	static int M; // 가로 (1~8)
	static int[][] map; // 사무실
	static int k; // cctv 갯수
	
	static List<CCTV> cctvs = new ArrayList<>();
	// 방향은 동 남 서 북
	static int[] dy = {0, 1, 0, -1}; // 세로
	static int[] dx = {1, 0, -1, 0}; // 가로
	
	static int result = Integer.MAX_VALUE; // 사각 지대의 최소 크기
	
	public static void main(String[] args) throws IOException {
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str;
		str = br.readLine();
		
		N = Integer.parseInt(str.split(" ")[0]);
		M = Integer.parseInt(str.split(" ")[1]);
		
		map = new int[N][M];
		
		StringTokenizer st;
		for(int i=0; i<N; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			for(int j=0; j<M; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
				
				// cctv 위치, 종류 List에 삽입
				if(map[i][j] >= 1 && map[i][j] <=5) {
					cctvs.add(new CCTV(i, j, map[i][j]));
				}
			}
		}
		k = cctvs.size();
		// ======= 입력 끝 ========
		dfs(0);
		
		System.out.println(result);
	}
	
	// y,x가 지도 범위 벗어나는지 확인
	static boolean isMapRange(int y, int x) {
		boolean flag = true;
		
		if(y<0 || y>=N || x<0|| x>=M)
			return false;
		
		return flag;
	}
	
	// copyMap에서 cctv가 감시중인 곳을 7로 바꿔줌
	static int[][] setCCTV(int[][] map){
		
		CCTV cctv;
		int cy, cx; // 세로, 가로
		int ny, nx;
		for(int i=0; i<cctvs.size(); i++) {
			cctv = cctvs.get(i);
			
			cy = cctv.y;
			cx = cctv.x;
			
			// i번째 cctv의 방향들로 감시하는 구역 값(7)로 바꿔줌
			int dir;
			for(int j=0; j<cctv.dir.length; j++) {
				dir = cctv.dir[j];// 해당 방향
				
				ny = cy;
				nx = cx;
				// j번째 방향을 7로 바꿔줌
				while(true) {
					ny += dy[dir];
					nx += dx[dir];
					
					// ny, nx 범위 확인(사무실내인지)
					if(isMapRange(ny, nx)) {
						// 빈 공간이면 감시 공간으로 바꿔줌
						if(map[ny][nx] == 0)
							map[ny][nx] = 7;
						// 벽에 도달하면 멈춤
						else if(map[ny][nx] == 6)
							break;
					}
					// 사무실 범위 벗어나면 멈춤
					else {
						break;
					}
					
				}
			}
		}
		
		return map;
	}
	
	// CCTV들 방향설정 및 감시하고 있는 영역을 구하는 함수
	static void dfs(int depth) {
		
		if(depth >= k) {
			
			// copyMap 복사
			int[][] copyMap = new int[N][M];
			for(int i=0; i<N; i++) {
				for(int j=0; j<M; j++) {
					copyMap[i][j] = map[i][j];
				}
			}
			
			// 정해진 방향으로 copyMap 재설정(cctv 감시중인 곳 #대신 7로)
			copyMap = setCCTV(copyMap);
			
			// 사각지대 갯수 구하기(0인 곳)
			int sum = 0;
			for(int i=0; i<N; i++) {
				for(int j=0; j<M; j++) {
					//System.out.print(copyMap[i][j]);
					if(copyMap[i][j] == 0)
						sum++;
				}
				//System.out.println();
			}
			//System.out.println();
			// 최소 사각지대 갯수 구하기
			result = Math.min(result, sum);
		}
		else {
			
			// 각 cctv 방향 설정
			CCTV cctv;
			cctv = cctvs.get(depth);
			
			int[] temp = cctv.dir.clone();
			// 1,3,4번 cctv인 경우 4방향
			if(cctv.kinds==1 || cctv.kinds==3 || cctv.kinds==4) {
				for(int j=0; j<4; j++) {
					
					for(int k=0; k<cctv.dir.length; k++) {
						cctv.dir[k] += j;
						cctv.dir[k] = cctv.dir[k] % 4;
					}
					dfs(depth+1);
					// dir 원래대로 돌려주기
					cctv.dir = temp.clone();
				}
			}
			// 2번 cctv인 경우 2방향 가능 (상하, 좌우)
			else if(cctv.kinds==2) {
				for(int j=0; j<2; j++) {
					for(int k=0; k<cctv.dir.length; k++) {
						cctv.dir[k] += j;
						cctv.dir[k] = cctv.dir[k] % 4;
					}
					dfs(depth+1);
					// dir 원래대로 돌려주기
					cctv.dir = temp.clone();
				}
			}
			// 5번 cctv인 경우 1방향
			else if(cctv.kinds==5) {
				dfs(depth+1);
			}
			
		}
		
	}

}

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

 

14891번: 톱니바퀴

첫째 줄에 1번 톱니바퀴의 상태, 둘째 줄에 2번 톱니바퀴의 상태, 셋째 줄에 3번 톱니바퀴의 상태, 넷째 줄에 4번 톱니바퀴의 상태가 주어진다. 상태는 8개의 정수로 이루어져 있고, 12시방향부터 �

www.acmicpc.net

문제

구현(Simulation) 문제이다.

 

해결 방안

문제 구성을 크게 1. 입력 받는 부분, 2. 각 톱니바퀴 회전 방향 입력에 따른 전체 톱니바퀴 회전 방향 구하기, 3. 회전 방향에 따른 각 톱니바퀴 상태 재설정, 4. 다 회전한 후 톱니바퀴 점수 합 구하기 로 4가지로 구성하였다.

1. 입력 받는 부분은 그냥 입력 받으면 되는 부분이라 넘어갑니다.

2. 각 톱니 바퀴 회전 방향 입력에 따른 전체 톱니바퀴 회전 방향 구하는 것은 톱니바퀴가 4개로 설정되어 있어 전체를 직접 구현하였다.
(톱니바퀴 1이 회전할때, 톱니바퀴 2가 회전할때, 톱니바퀴 3이 회전할때, 톱니바퀴 4가 회전할 때)
각각 톱니바퀴 회전 방향이 주어졌을 때 위의 4가지 경우를 모두 구해주었다.
그리고 static 으로 int형 gearDir[]에 각 톱니바퀴들이 회전해야 하는 방향을 넣어주었다. (1:시계, 0:정지, -1:반시계) 

3. 회전 방향에 따른 각 톱니바퀴 상태 재설정하는 부분은 위에서 저장한 gearDir[]를 참고하여 각각의 톱니바퀴들의 상태를 재설정해주었다.
gearDir[gearIdx]가 0이면 해당 톱니바퀴는 정지되야 하므로 넘어가주고 1과 -1일때 각각의 상태를 gear[][]에 재설정해주었다.

4. gear[][]를 가지고 문제의 출력에 맞게 점수를 구해준다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

// 톱니바퀴
// 총 8개의 톱니를 가지고 있는 톱니바퀴 4개가 일렬로 놓여져 있다.
// 톱니는 N극 또는 S극 중 하나
// 톱니바퀴가 다른 톱니바퀴와 서로 맞닿는 톱니의 극이 다르면 서로 반대방향으로 회전하게 됨

public class Main {
	
	// 톱니바퀴 4개의 톱니 상태(12시 방향부터 시작)(N극은 0, S극은 1)
	static int[][] gear = new int[5][9]; //[1~4][1~8] 
	static int K; // 회전 횟수(1~100)
	static int gearIdx; // 회전할 톱니(1~4)
	static int dir; // 입력 받은 값
	static int[] gearDir = new int[5]; // 각 톱니바퀴들의 회전 방향 (시계방향: 1, 반시계방향:-1 +  정지:0)
	static int result; // 네 톱니바튀의 점수의 합

	public static void main(String[] args) throws IOException {
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		String str;
		for(int i=1; i<=4; i++) {
			str = br.readLine();
			for(int j=1; j<=8; j++) {
				gear[i][j] = Integer.parseInt(str.split("")[j-1]);
			}
		}
		
		K = Integer.parseInt(br.readLine());
		StringTokenizer st;
		for(int i=0; i<K; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			
			gearIdx = Integer.parseInt(st.nextToken());
			dir = Integer.parseInt(st.nextToken());
			
			caculateGearDir();
			rotateGear();
		}
		
		result = 0;
		caculateScore();
		System.out.println(result);
		
	}
	
	// 입력 받은 gearIdx, dir를 이용한 다른 기어 회전 방향 구하기
	static void caculateGearDir(){
		// 초기화
		for(int i=1; i<=4; i++) {
			gearDir[i] = 0;
		}
		
		// 해당 gear방향 입력받은 dir 값으로 설정
		gearDir[gearIdx] = dir;

		// 다른 톱니바퀴와 만나는 부분 3, 7
		switch(gearIdx) {
		
			// 톱니바퀴 1이 회전할 경우 1->2->3->4
			case 1:
				// 톱니바퀴 1이 회전하고 맞물린 부분이 값이 다를때 톱니바퀴2 반대방향으로 회전
				if(gearDir[1] != 0 && gear[1][3] != gear[2][7] )
					gearDir[2] = gearDir[1] * -1;

				// 톱니 2와 톱니 3
				if(gearDir[2] != 0 && gear[2][3] != gear[3][7] )
					gearDir[3] = gearDir[2] * -1;
				
				// 톱니 3과 톱니 4
				if(gearDir[3] != 0 && gear[3][3] != gear[4][7] )
					gearDir[4] = gearDir[3] * -1;
					
				break;
			
			// 톱니 바퀴 2가 회전하는 경우 2->3->4 2->1
			case 2:
				// 톱니 2와 1
				if(gearDir[2] != 0 && gear[2][7] != gear[1][3])
					gearDir[1] = gearDir[2] * -1;
				
				// 톱니 2와 3
				if(gearDir[2] != 0 && gear[2][3] != gear[3][7])
					gearDir[3] = gearDir[2] * -1;
				
				// 톱니 3과 4
				if(gearDir[3] != 0 && gear[3][3] != gear[4][7])
					gearDir[4] = gearDir[3] * -1;
				
				break;
			
			// 톱니 바퀴 3이 회전하는 경우 3->4 3->2->1
			case 3:
				// 톱니 3과 4
				if(gearDir[3] != 0 && gear[3][3] != gear[4][7])
					gearDir[4] = gearDir[3] * -1;
				
				// 톱니 3과 2
				if(gearDir[3] != 0 && gear[3][7] != gear[2][3])
					gearDir[2] = gearDir[3] * -1;
				
				// 톱니 2와 1
				if(gearDir[2] != 0 && gear[2][7] != gear[1][3])
					gearDir[1] = gearDir[2] * -1;
				
				break;
			
			// 톱니 바퀴 4가 회전하는 경우 4->3->2->1
			case 4:
				// 톱니 4와 3
				if(gearDir[4] != 0 && gear[4][7] != gear[3][3])
					gearDir[3] = gearDir[4] * -1;
				
				// 톱니 3과 2
				if(gearDir[3] != 0 && gear[3][7] != gear[2][3])
					gearDir[2] = gearDir[3] * -1;
				
				// 톱니 2와 1
				if(gearDir[2] != 0 && gear[2][7] != gear[1][3])
					gearDir[1] = gearDir[2] * -1;
				
				break;
		}
			
	}
	
	// 각 톱니바퀴 각 gearDir에 맞게 회전
	static void rotateGear() {
		int[] tempGear;
		
		// 모든 톱니바퀴 gearDir에 맞게 회전시켜줌
		for(int i=1; i<=4; i++) {
			
			tempGear = new int[9];
			
			// 해당 톱니바퀴가 회전 안할 때(0)는 넘어감
			if(gearDir[i] == 0)
				continue;
			
			// 해당 톱니바퀴가 시계방향(1)
			if(gearDir[i] == 1) {
				tempGear[1] = gear[i][8];
				for(int j=2; j<=8; j++) {
					tempGear[j] = gear[i][j-1];
				}
			}
			// 해당 톱니바퀴가 반시계방향(-1)
			else if(gearDir[i] == -1) {
				for(int j=1; j<=7; j++) {
					tempGear[j] = gear[i][j+1];
				}
				tempGear[8] = gear[i][1];
			}
			
			// 회전한 배열을 gear에 다시 저장해줌
			gear[i] = tempGear.clone();
		}
	}
	
	// K번 회전시킨 이후에 네 톱니바퀴 점수의 합 출력
	//1번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 1점
	//2번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 2점
	//3번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 4점
	//4번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 8점
	static void caculateScore() {
		
		if(gear[1][1] == 1)
			result += 1;
		if(gear[2][1] == 1)
			result += 2;
		if(gear[3][1] == 1)
			result += 4;
		if(gear[4][1] == 1)
			result += 8;
	}
}

데이터베이스 면접 질문

# 데이터베이스 기본 내용

Q. Database와 DBMS란?

A. Database는 사용자가 필요한 정보를 얻기 위해 논리적으로 연관된 데이터를 모아 구조적으로 통합해 놓은 것입니다.

DBMS는 Database Management System의 약자로 사용자와 데이터베이스를 연결시켜주는 소프트웨어입니다. 즉 DB를 관리하기 위한 시스템입니다. (대표적으로 구성, 조작, 제어 등의 기능을 가집니다.)

Q. SQL이란? SQL의 종류는?

A. SQL(Structured Query Language)는 구조적 질의 언어로 해당 질의 언어를 통해 데이터베이스를 제어하고 관리할 수 있다.

SQL 종류로는 DDL(Data Definition Language), DML(Data ManipulationLanguage), DCL(Data Control Language)이 있다. DDL은 데이터베이스 구조를 정의, 수정, 삭제하는 언어이며 create, alter, drop 등이 있다. DML은 데이터베이스 내의 자료 검색, 삽입, 갱신, 삭제를 위한 언어로 select, delete, update, insert가 있다. DCL은 데이터에 대해 무결성을 유지, 병행 수행 제어, 보호와 관리를 위한 언어로 commit, rollback, grant, revoke가 있다.

Q. DB에서 Index를 사용하는 이유는?

A. 인덱스(Index)는 데이터를 논리적으로 정렬하여 검색과 정렬 작업의 속도를 높이기 위해 사용된다. 예를 들면, 책에서 가장 빨리 내용을 찾는 방법은 책의 뒤편의 색인을 보는 것과 같다.

기본키에 대해서는 항상 DBMS가 내부적으로 정렬된 목록을 관리하기에 특정 행을 가져올 때 빠르게 처리된다. 하지만, 다른 열의 내용을 검색하거나 정렬시에는 하나하나 대조를 해보기 때문에 시간이 오래걸린다. (이를 인덱스로 정의해두면 검색속도가 향상된다.)

단점: 인덱스를 사용하면 데이터를 가져오는 작업의 성능은 향상시킬 수 있지만 데이터 삽입, 변경 등이 일어날 때 매번 인덱스가 변경되기 때문에 성능이 떨어질 수 있다.

사용대상 : 데이터 필터링과 정렬에 사용되므로, 데이터를 특정한 순서로 자주 정렬한다면 인덱스를 사용하기에 적합


# 무결성

Q. 무결성이란?

A. 무결성이란 데이터의 정확성, 일관성, 유효성을 유지하는 것을 말한다. 데이터의 무결성을 유지하기 위해 DBMS에서는 크게 3가지 종류로 구분한다. (개체, 참조, 도메인 무결성 가장 중요)

- 개체 무결성 : 기본키로 선택된 필드는 빈 값을 허용하지 않는다. (PK는 NULL이 되면 안된다.)
- 참조 무결성 : 서로 참조 관계에 있는 두 테이블의 데이터는 항상 일관된 값을 유지한다. (FK의 값은 NULL이거나 참조하고 있는 컬럼에 있는 값이여야 한다.)
- 도메인 무결성 : 테이블에 존재하는 필드의 무결성을 보장하기 위한 것으로 올바른 데이터가 입력됬는지를 체크하는 것이다. (예를 들면 성과 같은 컬럼의 값은 남/여 만 가능)
- 고유 무결성 : 특정 속성에 대해 고유한 값을 가지도록 조건이 주어진 경우 그 속성값은 모두 고유한 값을 가진다. 같으면 안된는 것
- NULL 무결성 : 특정 속성값에 NULL이 올 수 없다는 조건이 주어진 경우 그 속성값은 NULL이 될 수 없다는 제약조건
- 키 무결성 : 한 릴레이션에는 최소한 하나의 키가 존재해야하는 제약조건

Q. 무결성을 유지하려고 하는 이유?

A. 무결성이 유지가 되어야 DB에 저장된 데이터 값과 거기에 해당하는 현실 세계의 실제값이 일치하는지 신뢰할 수 있기 때문이다.


# 트랜잭션 (동시성 제어, 데이터베이스 장애 및 회복)

Q. 트랜잭션이 뭔가요?

A. 하나의 논리적 기능을 수행하기 위한 작업의 단위로, DB의 일관된 상태를 또 다른 일관된 상태로 변환시키는 기능을 수행한다.

트랜잭션은 전체가 수행되거나 또는 전혀 수행되지 않아야한다.

Q. 트랜잭션의 성질을 말씀해보세요.

A. ACID라 불리는 총 네가지 성질을 가지고있습니다.

  • Atomicity는 트랜잭션의 연산이 DB에 모두 반영되던지 전혀 반영이되지 않던지 둘중에 하나만 수행해야한다.(원자성)

  • Consistency는 트랜잭션이 성공적으로 완료된 후에는 언제나 일관성 있는 DB상태로 변환되어야한다.(일관성)
    (예를 들면 A에서 B로 돈 이체를 할때 A와 B 계좌의 돈의 총합은 같아야한다.)

  • Isolation은 수행중인 트랜잭션이 완전히 완료되기 전에는 다른 트랙잭션에서 수행 결과를 참조할 수 없다.(독립성)
    (따라서 트랜잭션이 동시 접근하는 데이터에대한 제어가 필요하다.)

  • Durablility는 성공적으로 완료된 트랜잭션의 결과는 시스템이 고장나더라도 영구적으로 반영되어야 한다.(영속성)
    (트랜잭션이 정상적으로 완료(commit)된 경우에는 하드디스크(데이터베이스)에 확실히 기록해야하며, 부분완료된 경우 작업을 취소해야합니다.)

Q. 트랜잭션을 병행으로 처리(동시성 제어)하려고 할 때 발생할 수 있는 문제를 설명해보시오.

A.

  • 갱신 내용 손실 : 동시에 하나의 데이터가 갱신될 때 하나의 갱신이 누락되는 경우

  • 현황 파악 오류 : 하나의 데이터 갱신이 끝나지 않은 시점에서 다른 트랜잭션이 해당 데이터를 조회하는 경우

  • 모순성 : 두 트랜잭션이 동시에 실행될 때 데이터베이스가 일관성이 없는 모순된 상태로 남는 문제

  • 연쇄 복귀 : 두 트랜잭션이 하나의 레코드를 갱신할 때 하나의 트랜잭션이 롤백하면 다른 하나의 트랜잭션 마저 롤백이 되는 문제

Q. 트랜잭션을 병행으로 처리할 때 위와 같은 문제를 방지하기 위한 방법을 설명하시오.

A. 로킹 제어 기법을 사용한다.

어떤 트랜잭션이 특정 DB의 데이터를 사용할 때 DB의 일정부분을 Lock시키고 트랜잭션이 완료될때 해당부분을 Unlock시키는 방법이다. 종류는 크게 두가지가 있는데 공유 로킹은 Lock한 부분을 읽기는 가능하지만 쓰기는 불가능한 것이고 배타 로킹은 읽기,쓰기 둘다 불가능하게 한 것이다.

Q. 그렇다면 이 로킹 단위를 크게했을 때와 작게 했을 때의 차이점을 설명하시오.

A. 로킹 단위가 크면 그만큼 관리가 쉽지만 병행성이 떨어진다. 로킹단위가 작으면 관리가 어렵고 오버헤드가 증가하지만, 병행성이 올라간다.

Q. 로킹 제어가 일으킬 수 있는 문제점은 무엇인가?

A. 로킹단위에 따라 다르겠지만 트랜잭션의 직렬화 가능성이 높아진다.(병행처리하나마나 할 수도있다.) 또 데드락이 발생할 수 있다.

Q. 데드락에 대해서 설명해보세요.

T1 : write(A) read(B)

T2 : read(B) read(A)

위와 같은 트랜잭션이 있다고 하면 T1은 A를 로킹해두고 B의 로킹해제를 기다려야하고 T2는 B를 로킹해두고 A를 기다려야한다. 이 때 두 트랜잭션이 무한정 대기해야하는 상황이 발생하는데 이것을 데드락이라고 한다. (해결방법 : 이 경우 T1, T2중 하나를 ROLLBACK하고 나머지 하나를 완료시킨 후 ROLLBACK한 트랜잭션을 다시 실행시킨다.)

Q. 그럼 데드락을 안 생기게 하는 방법을 설명해보시오.

A.

서비스 별로 해결하는 방법이 매우 다른데 일반적으로는 데드락 탐지나 회피를 적용시키면 된다.

탐지인 경우 알고리즘을 통해 매번 데드락인지 아닌지 검사를 해야하므로 코스트가 크다.

회피인 경우 시분할 처리를하여 T1이 끝나면 T2가 실행시키게도 하면된다.

Facebook 처럼 write 보다 read가 월등히 많은 경우 Read용 DB를 slave로 두고 로드를 모두 몰아주고 write를 Master로 보내고 DB를 동기화 할 수도 있다.

또 다른 해결기법으론 로킹 제어기법이 아닌 타임스탬프 기법을 사용한다. 트랜잭션의 식별자로 타임스탬프를 지정하며 순서를 미리 선택한다. 트랜잭션이 대기하지 않고 바로 실행은 하나 높은 확률로 ROLLBACK이 일어나며 연쇄 복귀를 초래할 수 있다.

Q. COMMIT과 ROLLBACK에 대해 설명해보세요.

A. DML을 수행하고 난 뒤 COMMIT은 해당 트랜잭션으로 반영된 DB 변경사항을 저장 하는 것이고 ROLLBACK은 해당 트랜잭션으로 반영된 DB 변경사항을 취소 하는 것이다.

Q. 데이터베이스 장애의 유형

A. 

  • 트랜잭션 장애: 트랜잭션의 실행 시 논리적인 오류로 발생할 수 있는 에러 상황

  • 시스템 장애: H/W 시스템 자체에서 발생할 수 있는 에러 상황

  • 미디어 장애: 디스크 자체의 손상으로 발생할 수 있는 에러 상황

Q. 데이터베이스 회복 기법에 대해 설명하시오.

A.

  • 로그기반 회복기법

  • 지연 갱신 회복 기법

    • write 연산 지연, 로그에 DB변경 내역 저장

    • 트랜잭션 완료시 로그를 보고 write 연산 수행

    • 트랜잭션 완료시 장애 발생 : REDO만 실행

    • 트랜잭션 미완료시 장애 발생 : 로그 무시

  • 즉시 갱신 회복 기법

    • 즉시 DB 변경, 로그에 기록

    • 장애 발생 시 로그에 기반하여 UNDO 실행

  • 체크포인트 회복기법

  • 체크 포인트를 지정하여 장애발생시 체크포인트까지 UNDO 실행 후 다시 REDO 실행

  • 그림자 페이징 회복 기법

  • 하드디스크에 그림자 페이지를 만들고 저장해두고 장애발생시 하드디스크에 있는 페이지로 주메모리 페이지 변경

  • 장애 미발생시 그림자 페이지 테이블은 삭제

 

* 트랜잭션 설명 및 예제가 가장 잘 나와있다고 생각되는 사이트

https://mangkyu.tistory.com/30?category=761304


# 정규화

Q. DB Normalization(정규화)란?

A. 데이터베이스 정규화란 데이터의 중복을 줄이고 무결성을 향상 시키는 등 여러 목적을 달성하기 위해 관계형 데이터베이스를 정규화된 형태로 재디자인하는 것을 말한다.

Q. DB Normalization(정규화)의 목적은?

A.

  • 불필요한 데이터를 제거, 데이터의 중복을 최소화 하기 위해서

  • 데이터베이스 구조 확장 시 재디자인을 최소화

  • 다양한 관점에서의 query를 지원하기 위해서

  • 무결성 제약조건의 시행을 간단하게 하기 위해서

  • 각종 이상현상을 방지하기 위해서, 테이블의 구성을 논리적이고 직관적으로 한다.

Q. 이상현상 이란?

A.
좋은 관계형 데이터베이스를 설계하는 목적 중 하나가 정보의 이상 현상(Anomaly) 이 생기지 않도록 고려해 설계하는 것이다.
이상 현상은 갱신 이상(Modification Anomaly), 삽입 이상(Insertion Anomaly), 삭제 이상(Deletion Anomaly) 으로 구성된다.
각각을 간략하게 설명하면 다음과 같다.

  • 갱신 이상(Modification Anomaly): 반복된 데이터 중에 일부를 갱신 할 시 데이터의 불일치가 발생한다.

  • 삽입 이상(Insertion Anomaly): 불필요한 정보를 함께 저장하지 않고서는 어떤 정보를 저장하는 것이 불가능하다.

  • 삭제 이상(Deletion Anomaly): 필요한 정보를 함께 삭제하지 않고서는 어떤 정보를 삭제하는 것이 불가능하다.

Q. 정규화 과정?

A. 정규화는 1정규화 ~ 6정규화 까지 있지만, 실무에서는 대체로 1~3 정규화까지의 과정을 거친다.

  • 제 1정규화 : 각 컬럼들은 값이 원자값을 가지게 바꾼다.

  • 제 2정규화 : 테이블의 모든 컬럼에서 부분 함수적 종속을 제거하는 것

  • 제 3정규화 : 기본키를 제외한 속성들 간의 이행적 함수 종속을 없애는 것

제 1정규화(First Normal Form, 1NF)

테이블(Relation)이 제 1정규형을 만족했다는 것은 아래 세 가지 조건를 만족했다는 것을 의미한다.

  1. 어떤 Relation에 속한 모든 Domain이 원자값(atomic value)만으로 되어 있다.

  2. 모든 attribute에 반복되는 그룹(repeating group)이 나타나지 않는다.

  3. 기본 키를 사용하여 관련 데이터의 각 집합을 고유하게 식별할 수 있어야 한다.

제 2정규화(Second Normal Form, 2NF)

제 2정규화를 수행 했을 경우 테이블의 모든 컬럼이 완전 함수적 종속을 만족한다.(부분 함수적 종속을 모두 제거되었다.) 이를 이해하기 위해서는 부분 함수적 종속과 완전 함수적 종속이라는 용어를 알아야 한다.

  • 함수적 종속: X의 값에 따라 Y값이 결정될 때 X -> Y로 표현하는데, 이를 Y는 X에 대해 함수적 종속 이라고 한다. 예를 들어 학번을 알면 이름을 알 수 있는데, 이 경우엔 학번이 X가 되고 이름이 Y가 된다. X를 결정자이라고 하고, Y는 종속자라고 한다. 다른 말로 X가 바뀌었을 경우 Y가 바뀌어야만 한다는 것을 의미한다.

  • 함수적 종속에서 X의 값이 여러 요소일 경우, 즉, {X1, X2} -> Y일 경우, X1와 X2가 Y의 값을 결정할 때 이를 완전 함수적 종속 이라고 하고, X1, X2 중 하나만 Y의 값을 결정할 때 이를 부분 함수적 종속 이라고 한다.

제 3정규화(Third Normal Form, 3NF)

테이블(Relation)이 제 3정규형을 만족한다는 것은 아래 두 가지 조건을 만족하는 것을 의미한다.

  1. Relation이 제 2정규화 되었다.(The relation is in second normal form)

  2. 기본 키(primary key)가 아닌 속성(Attribute)들은 기본 키에만 의존해야 한다.

Q. 역정규화를 하는 이유는 무엇인가요?

A. 정규화를 진행할 수록 하나의 테이블을 여러 테이블로 나누게 되는데, 만약 데이터 호출 시 여러 테이블을 불러서 JOIN을 해줘야한다면 이 비용도 만만치 않기 때문에 역정규화를 한다.

 

* DB 정규화 개념 설명 및 예제가 가장 잘 나와있다고 생각되는 사이트

https://wkdtjsgur100.github.io/database-normalization/


# 관계형 데이터베이스와 비 관계형 데이터베이스

Q. 관계형 데이터베이스와 비 관계형 데이터베이스 차이점에 대해 설명해보세요.

A. 관계형 데이터베이스란 테이블(table)로 이루어져 있으며, 이 테이블은 키(key)와 값(value)의 관계를 나타낸다.이처럼 데이터의 종속성을 관계(relationship)로 표현하는 것이 관계형 데이터베이스의 특징이다.

비 관계형 데이터베이스는 행과 열로 이루어진 테이블 형식 스키마를 사용하지 않는 데이터베이스이다. 저장되는 데이터 형식의 특정 요구 사항에 맞게 최적화된 저장소 모델을 사용하는 것이 특징이다. 흔히들 NoSQL(not only SQL)이라고 하며 데이터를 저장할 때 SQL문이 아닌 다른 프로그래밍 언어 및 구문을 사용한다.

Q. RDBMS과 비교하였을 때 NoSQL의 장점을 설명해보세요.

A. 가장 큰 장점이라면 JOIN 처리가 없기 때문에 스케일 아웃을 통한 노드 확장 용이하다는 점이다. 뿐만아니라 가변적인 데이터구조로 데이터를 저장할 수 있어서 훨씬 더 유연성이 높다. 단점으로는 다양하고 복잡한 쿼리가 불가능하고 일관성을 항상 보장할 수 없는 것을 꼽을 수 있다.

속도적인 측면에서는 대표적인 RDBMS인 MySQL이나 ORACLE이 워낙 최적화가 잘 되어있기도하고 주어진 상황에 따라 아주 케이스바이케이스라 뭐가 더 좋다라고는 하기 어렵다.

Q. NoSQL이란?

A.

    • NoSQL 데이터베이스는 관계형 데이터베이스(RDB)보다 덜 제한적인 일관성 모델을 이용하는 데이터의 저장 및 검색을 위한 매커니즘을 제공한다.

    • 단순 검색 및 추가 작업을 위한 매우 최적화된 키-값 저장 공간을 사용한다.

    • 빅데이터 시대에 따라 많은 양의 데이터를 효율적으로 처리하기 위해 등장하였다. (분산처리, 빠른쓰기 및 데이터의 안정성)

    • 분산형 구조를 통해 여러 대의 서버에 분산해 저장하고, 분산시에는 데이터를 상호 복제에 특정 서버에 장애가 발생했을 때에도 데이터 유실이나 서비스 중지가 없는 형태의 구조를 갖고 있다.

Q. 어떤상황에서 NoSQL을 쓰는 것이 더 적합한가?

A. 비정형 데이터를 저장해야할 때 가장 적합하다. 검색기능이 있을 때도 좋다고들하는데 의견이 분분하다. 케바케라는 말이 적절할 것 같다.



 

* 참고자료

https://kadamon.tistory.com/21

https://91ms.tistory.com/2?category=711086

https://mangkyu.tistory.com/30?category=761304

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

 

14890번: 경사로

첫째 줄에 N (2 ≤ N ≤ 100)과 L (1 ≤ L ≤ N)이 주어진다. 둘째 줄부터 N개의 줄에 지도가 주어진다. 각 칸의 높이는 10보다 작거나 같은 자연수이다.

www.acmicpc.net

문제

Simulation 문제이다. Queue를 사용하여 구현하다가 올라가는 경사에서 막혀서 다른사람 풀이를 참고하였다.

 

해결 방안

주어진 조건에 맞도록 구현하는 Simulation 문제이다.

1. 각 행과 열마다 지나길 수 있는 길인지 체크해서 갯수를 세준다.
2. 해당 행과 열을 1차원 배열로 만들어서 체크하기 쉽게 만든다. (checkPath()함수)
조건이 맞지 않는 경우 false를 리턴해준다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

// 경사로
public class Main {

	static int N; // 지도 크기 NxN (2~100)
	static int L; // 경사로 길이 (1~N)
	static int[][] map; // 지도
	static boolean[][] visited; // 경사로 놓은지 확인
	static int pathCnt;
	
	public static void main(String[] args) throws IOException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str;
		str = br.readLine();
		N = Integer.parseInt(str.split(" ")[0]);
		L = Integer.parseInt(str.split(" ")[1]);
		pathCnt = 0;
		
		map = new int[N][N];
		visited = new boolean[N][N];
		
		StringTokenizer st;
		for(int i=0; i<N; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			
			for(int j=0; j<N; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		// ===== 입력 끝 =====
		
		// 가능한 길인지 아닌지 체크
		for(int i=0; i<N; i++) {
			if(checkPath(i, 0, true)) // 행
				pathCnt++;
			
			if(checkPath(0, i, false)) // 열
				pathCnt++;
		}
		
		System.out.println(pathCnt);
	}

	// flag: true 행 검사
	// flag: false 열 검사
	static boolean checkPath(int x, int y, boolean flag) {
		
		int[] height = new int[N];
		boolean[] visited = new boolean[N];
		
		for(int i=0; i<N; i++) {
			if(flag)
				height[i] = map[x][i];
			else
				height[i] = map[i][y];
		}
		
		for(int i=0; i<N-1; i++) {
			// 높이가 같을 때
			if(height[i] == height[i+1]) {
				continue;
			}
			// 내려가는 경사
			else if(height[i]-height[i+1] == 1) {
				
				for(int j=i+1; j<=i+L; j++) {
					// 범위 넘어가거나 칸의 높이가 다르거나 이미 경사로가 있는 경우
					if(j>=N || height[i+1]!=height[j] || visited[j]) return false;
					visited[j] = true;
				}
			}
			// 올라가는 경사
			else if(height[i]-height[i+1] == -1) {
				
				for(int j=i; j>i-L; j--) {
					// 범위 넘어가거나 칸의 높이가 다르거나 이미 경사로가 있는 경우
					if(j<0 || height[i]!=height[j] || visited[j]) return false;
					visited[j] = true;
				}
			}
			// 높이가 2칸 이상 차이 날때 (길x)
			else {
				return false;
			}
		}
		
		return true;
	}
	
}

 

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

 

14889번: 스타트와 링크

예제 2의 경우에 (1, 3, 6), (2, 4, 5)로 팀을 나누면 되고, 예제 3의 경우에는 (1, 2, 4, 5), (3, 6, 7, 8)로 팀을 나누면 된다.

www.acmicpc.net

문제

DFS + 구현 으로 문제를 해결하였다.

 

해결 방안

전체적으로 보면 1. 스타트 팀과 링크 팀 반으로 사람을 나누는 모든 경우의 수와 2. 나눠진 팀의 점수 차이를 구하는 두가지를 구현해야한다.

1. 스타트 팀과 링크 팀 반으로 사람을 나누는 모든 경우의 수는 DFS 탐색을 이용하여 구현하였다.
기저사례가 N/2 일 때까지 DFS 탐색을 하며 N/2 개의 visited를 true로 만들어준다.
caculateDifference()함수에서 visited가 true인 경우는 start 팀에 false인 경우 link 팀에 넣어주었다.

2. 나눠진 팀을 가지고 각 팀의 점수를 getTeamAbility() 함수를 이용하여 구해주었다.
그리고 각각 구해진 팀의 점수를 빼서 절댓값을 구해주었다.

그리고 마지막으로 min인 경우를 찾았다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

// 스타트와 링크
// 능력치 차이의 최소화
public class Main {

	static int N; // 사람수(4~20, 짝수)
	static int[][] S; // 능력치
	static boolean[] visited;
	
	static int minDifference = Integer.MAX_VALUE;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		N = Integer.parseInt(br.readLine());
		
		S = new int[N+1][N+1];
		visited = new boolean[N+1];
		String str;
		StringTokenizer st;
		for(int i=1; i<N+1; i++) {
			str = br.readLine();
			st = new StringTokenizer(str, " ");
			for(int j=1; j<N+1; j++) {
				S[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		// ========= 입력 끝 ==========
		
		dfs(0, 0);
		
		System.out.println(minDifference);
	}
	
	static void dfs(int v, int len) {
		if(len == N/2) {
			// 스타트팀, 링크팀 능력치 차이 구하기
			int difference = caculateDifference();
			// 능력치 차이 최솟값 구하기
			minDifference = Math.min(minDifference, difference);
		}
		else {
			for(int i=v+1; i<N+1; i++) {
				if(!visited[i]) {
					visited[i] = true;
					dfs(i, len+1);
					visited[i] = false;
				}
			}
		}
	}
	
	// 팀 점수 차이 구하기
	static int caculateDifference() {
		
		int[] start = new int[N/2 + 1];
		int[] link = new int[N/2 + 1];
		
		int si = 1; int li = 1;
		for(int i=1; i<N+1; i++) {
			if(visited[i]) start[si++] = i;
			else link[li++] = i;
		}
		
		// 각 팀 점수 구하기
		int startAbility = getTeamAbility(start);
		int linkAbility = getTeamAbility(link);
		
		int difference = Math.abs(startAbility-linkAbility);
		
		return difference;
	}

	// 각팀 점수 구하기
	static int getTeamAbility(int[] team) {
		
		int teamAbility = 0;
		
		for(int i=1; i<team.length; i++) {
			for(int j=i+1; j<team.length; j++) {
				teamAbility += S[team[i]][team[j]];
				teamAbility += S[team[j]][team[i]];
			}
		}
		
		return teamAbility;
	}
}

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

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, ��

www.acmicpc.net

문제

브루트포스 문제 인데 DFS 탐색을 이용하여 문제를 해결하였다.

 

해결 방안

숫자의 위치는 고정이고 연산자 우선순위도 무시하므로 숫자간 모든 경우의 연산자를 넣어 각각의 sum값들을 비교하여 최대 최소값을 구하는 문제이다.

덧셈, 뺄셈, 곱셈, 나눗셈의 갯수의 합이 N-1 개이므로 각각의 갯수들을 고려하여 DFS 탐색을 해주면된다.
그리고 기저사례를 depth가 N-1인 경우로 하며 계산된 sum값의 max, min 값을 구해준다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

// 연산자 끼워넣기
// 연산자 우선 순위 무시하고 앞에서부터 진행
// 나눗셈은 몫만 취함(음수 이면 양수로 바꾼뒤 몫을 취하고 그 몫을 음수로 바꿈)
// 식의 결과가 최대인 값과 최소인 값을 구함
public class Main {

	static int N; // 수의 갯수(2~11)
	static int[] a; // a1~an
	
	static int[] operatorCnts; // 덧셈, 뺄셈, 곱셈, 나눗셈
	
	// (-10억~10억)
	static int max = Integer.MIN_VALUE;
	static int min = Integer.MAX_VALUE;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		N = Integer.parseInt(br.readLine());
		a = new int[N+1];
		
		String str;
		str = br.readLine();
		StringTokenizer st = new StringTokenizer(str, " ");
		for(int i=1; i<N+1; i++) {
			a[i] = Integer.parseInt(st.nextToken());
		}
		
		str = br.readLine();
		operatorCnts = new int[4];
		for(int i=0; i<4; i++) {
			operatorCnts[i] = Integer.parseInt(str.split(" ")[i]);
		}
		
		// =========== 입력 끝 ============
		dfs(0, a[1]);
		
		System.out.println(max);
		System.out.println(min);
	}
	
	static void dfs(int depth, int sum) {

		if(depth >= N-1) {
			max = Math.max(max, sum);
			min = Math.min(min, sum);
			return;
		}
		else {
			// 덧셈
			if(operatorCnts[0] > 0) {
				operatorCnts[0]--;
				dfs(depth+1, sum+a[depth+2]);
				operatorCnts[0]++;
			}
			// 뺄셈
			if(operatorCnts[1] > 0) {
				operatorCnts[1]--;
				dfs(depth+1, sum-a[depth+2]);
				operatorCnts[1]++;
			}
			// 곱셈
			if(operatorCnts[2] > 0) {
				operatorCnts[2]--;
				dfs(depth+1, sum*a[depth+2]);
				operatorCnts[2]++;
			}
			// 나눗셈
			if(operatorCnts[3] > 0) {
				operatorCnts[3]--;
				if(sum < 0) {
					sum *= -1;
					dfs(depth+1, (sum/a[depth+2]) * -1);
					sum *= -1;
				}
				else {
					dfs(depth+1, sum/a[depth+2]);
				}
				operatorCnts[3]++;
			}
		}
	}
}

+ Recent posts