[시간 제한]

Java 8: 2.5 초

Java 8 (OpenJDK): 2.5 초

Java 11: 2.5 초

Kotlin (JVM): 2.5 초

 

[문제]

N(2 ≤ N ≤ 100,000)개의 정점으로 이루어진 트리가 주어진다. 트리의 각 정점은 1번부터 N번까지 번호가 매겨져 있으며, 루트는 1번이다.

두 노드의 쌍 M(1 ≤ M ≤ 100,000)개가 주어졌을 때, 두 노드의 가장 가까운 공통 조상이 몇 번인지 출력한다.

 

[입력]

첫째 줄에 노드의 개수 N이 주어지고, 다음 N-1개 줄에는 트리 상에서 연결된 두 정점이 주어진다. 그 다음 줄에는 가장 가까운 공통 조상을 알고싶은 쌍의 개수 M이 주어지고, 다음 M개 줄에는 정점 쌍이 주어진다.

 

[출력]

M개의 줄에 차례대로 입력받은 두 정점의 가장 가까운 공통 조상을 출력한다.

 

[예제]

예제 입력1 예제 출력1
15
1 2
1 3
2 4
3 7
6 2
3 8
4 9
2 5
5 11
7 13
10 4
11 15
12 5
14 7
6
6 11
10 9
2 6
7 6
8 13
8 15
2
4
2
1
3
1

 


>문제 풀이

LCA: Lowest Common Ancestor

최소 공통 조상 알고리즘에 대한 문제입니다.

문제의 예제를 트리로 만들어 놓고 이 트리를 기준으로 LCA에 대해 설명을 하자면,

어떤 노드 u, v에 대해 depth가 더 깊은 노드를 조상노드로 올리고 올려 depth를 맞추고, 그 위에 공통으로 가지는 부모 노드를 찾는 것입니다.

말로 하는 것보다 그림으로 보는게 더 직관적으로 이해가 가기 때문에, 그림으로 살펴보겠습니다.

u와 v를 보면 depth가 같지 않습니다.

u를 v의 depth와 같아질 때까지 부모노드를 타고 올라갑니다.

그러면, parent[u]= 4 와 v를 비교하게 됩니다.

이 두 정점의 공통 부모는 2번째 노드가 되는걸 알 수 있습니다.

그럼 u와 v를 기준으로 한 층씩 올라가며 부모노드를 찾아주면 될까요??

문제에서는 N이 100,000 M이 100,000까지 들어올 수 있습니다.

만약 트리가 한쪽으로 치우치기라도 한다면 정점 u, v에 대해, 한번씩 부모 노드로 옮겨가며 lca를 찾게되는데, 최악의 경우 O(N)의 연산이 필요합니다.

그리고 M개의 케이스가 들어오면 O(M*N) 이 되어버려서 시간초과가 발생할 수 있습니다.

이를 해결하기 위해 탐색의 방법을 살펴봐야하고, dp로 정점 V에 대한 K번째 부모노드 값을 메모이제이션을 해줄 필요가 있습니다.

탐색의 시간을 줄이는 것은 어떻게 효과적으로 트리를 접어갈 수 있냐는 것입니다.

이 코드에서는 부모 노드를 나타내기 위해 parent [ ] [ ] 2차원 배열을 사용했습니다.

* parent[K][V]: 노드 V의 2^K번째 부모 노드의 번호

그리고 이 parent[i][j] 배열을 채워줘야 하는데, 아래의 표를 보면 알 수 있듯이

parent[i][j]= parent[i-1][parent[i-1][j]] 의 점화식이 성립합니다.

*** 로직 ***

1) 들어오는 두 정점의( u, v ) depth를 비교하기 위해

dfs로 depth를 구하여 depth[N+1] 배열에 담아주었습니다.

2) 2^K 점프를 통해 두 정점의 depth를 맞춰주고

더 깊은 depth를 가진 정점 u를 2^k점프하여 (depth[u]- depth[v]) 보다 작거나 같게 맞춰줍니다.

3) 공통 부모 바로 아래까지 올려줍니다.

그러면 정점 u와 v의 부모는 lca(최소 공통 부모) 니까

return parent[0][u];

 

>전체 코드

 

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


public class Main {
	static int N, M, K; //N:정점개수, M:TC개수, K
	
	//LCA 관련 변수
	static int[] depth;
	static int[][] parent; // parent[K][V] 정점 V의 2^K번째 조상 정점 번호
						   // parent[K][V]= parent[K-1][parent[K-1][V]];
	
	//Tree 변수
	static ArrayList[] tree;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
		//1. 입력
		N= Integer.parseInt(br.readLine());
		
		//2^K>N인 K찾기
		K=0;
		for(int i=1; i<=N; i*=2) {
			K++;
		}
		
		//LCA 관련 변수 초기화
		depth= new int[N+1];
		parent= new int[K][N+1];
		
		//tree 변수 초기화
		tree= new ArrayList[N+1];
		for(int i=1; i<=N; i++) {
			tree[i]= new ArrayList<Integer>();
		}
		
		int a, b;
		for(int i=1; i<=N-1; i++) { //입력받고 트리 만들기
			st= new StringTokenizer(br.readLine());
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			
			//양방향 간선
			tree[a].add(b);
			tree[b].add(a);
		}
		
		//2. Depth 확인
		dfs(1, 1);
		
		//3. 2^N 까지 parent 채우기
		fillParent();
		
		//4. LCA 진행
		M= Integer.parseInt(br.readLine());
		StringBuilder sb= new StringBuilder();
		for(int i=1; i<=M; i++) {
			st= new StringTokenizer(br.readLine());
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			
			sb.append(lca(a, b)+"\n");
		}
		
		System.out.println(sb.toString());
		
	}// main
	
	//Depth를 구함
	public static void dfs(int node, int cnt) {
		//1. depth를 기록
		depth[node]= cnt;
		
		//2. 자식들의 depth를 기록
		int len= tree[node].size(); //자식 몇개인지
		for(int i=0; i<len; i++) {
			int next= (Integer)tree[node].get(i);
			//아직 깊이를 모르면 -> 미방문 노드
			if(depth[next]==0) {
				dfs(next, cnt+1);
				parent[0][next]= node; //next 노드의 2^0= 첫번째 부모를 기록
			}
		}
	}
	
	//부모 채우기
	static void fillParent() {
		for(int i=1; i<K; i++) {
			for(int j=1; j<=N; j++) {
				//j번 노드의 i번째 부모= j의 i-1번째 부모의 i-1번째 부모 
				parent[i][j]= parent[i-1][parent[i-1][j]];
			}
		}
	}
	
	//최소 공통 조상
	static int lca(int a, int b) {
		//1. depth[a]>=depth[b] 이도록 조정하기
		if(depth[a]<depth[b]) {
			int temp=a;
			a= b;
			b= temp;
		}
		
		//2. 더 깊은 a를 2^K 점프하여 depth를 맞추기
		for(int i=K-1; i>=0; i--) {
			if(Math.pow(2, i)<= depth[a]- depth[b]) {
				a= parent[i][a];
			}
		}
		
		//3. depth를 맞췄는데 같다면 종료
		if(a==b) return a;
		
		//4. a-b는 같은 depth이므로 2^K 점프하며 공통 부모 바로 아래까지 올리기
		for(int i=K-1; i>=0; i--) {
			if(parent[i][a]!= parent[i][b]) {
				a= parent[i][a];
				b= parent[i][b];
			}
		}
		return parent[0][a];
	}//lca	
}

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

 

11438번: LCA 2

첫째 줄에 노드의 개수 N이 주어지고, 다음 N-1개 줄에는 트리 상에서 연결된 두 정점이 주어진다. 그 다음 줄에는 가장 가까운 공통 조상을 알고싶은 쌍의 개수 M이 주어지고, 다음 M개 줄에는 정

www.acmicpc.net

 

[문제 설명]

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다.

-1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3

사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target이 매개변수로 주어질 때 숫자를 적절히 더하고 빼서 타겟 넘버를 만드는 방법의 수를 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 주어지는 숫자의 개수는 2개 이상 20개 이하입니다.
  • 각 숫자는 1 이상 50 이하인 자연수입니다.
  • 타겟 넘버는 1 이상 1000 이하인 자연수입니다.

<입출력 예>

numberstargetreturn

[1, 1, 1, 1, 1] 3 5

 

<입출력 예 설명>

문제에 나온 예와 같습니다.


>문제 풀이

 

 재귀를 이용해서 풀었습니다.

 

>전체 코드

 

class Solution {
    static int answer=0;
    public int solution(int[] numbers, int target) {
        dfs(numbers, 0, 0, target);
        return answer;
    }
    
    public void dfs(int[] num, int index, int value, int t){
        if(index==num.length){
            if(value==t) answer++;
        }else{
            int minus= value-num[index];
        	int plus= value+num[index];
            dfs(num, index+1, minus, t);
            dfs(num, index+1, plus, t);
        }
    }
}

https://programmers.co.kr/learn/courses/30/lessons/43165

 

코딩테스트 연습 - 타겟 넘버

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+1+1+1 = 3 +1-1+1+1+

programmers.co.kr

 

[문제]

로봇 청소기가 주어졌을 때, 청소하는 영역의 개수를 구하는 프로그램을 작성하시오.

로봇 청소기가 있는 장소는 N×M 크기의 직사각형으로 나타낼 수 있으며, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 벽 또는 빈 칸이다. 청소기는 바라보는 방향이 있으며, 이 방향은 동, 서, 남, 북중 하나이다. 지도의 각 칸은 (r, c)로 나타낼 수 있고, r은 북쪽으로부터 떨어진 칸의 개수, c는 서쪽으로 부터 떨어진 칸의 개수이다.

로봇 청소기는 다음과 같이 작동한다.

  1. 현재 위치를 청소한다.
  2. 현재 위치에서 현재 방향을 기준으로 왼쪽방향부터 차례대로 탐색을 진행한다.
    1. 왼쪽 방향에 아직 청소하지 않은 공간이 존재한다면, 그 방향으로 회전한 다음 한 칸을 전진하고 1번부터 진행한다.
    2. 왼쪽 방향에 청소할 공간이 없다면, 그 방향으로 회전하고 2번으로 돌아간다.
    3. 네 방향 모두 청소가 이미 되어있거나 벽인 경우에는, 바라보는 방향을 유지한 채로 한 칸 후진을 하고 2번으로 돌아간다.
    4. 네 방향 모두 청소가 이미 되어있거나 벽이면서, 뒤쪽 방향이 벽이라 후진도 할 수 없는 경우에는 작동을 멈춘다.

로봇 청소기는 이미 청소되어있는 칸을 또 청소하지 않으며, 벽을 통과할 수 없다.

 

[입력]

첫째 줄에 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 50)

둘째 줄에 로봇 청소기가 있는 칸의 좌표 (r, c)와 바라보는 방향 d가 주어진다. d가 0인 경우에는 북쪽을, 1인 경우에는 동쪽을, 2인 경우에는 남쪽을, 3인 경우에는 서쪽을 바라보고 있는 것이다.

셋째 줄부터 N개의 줄에 장소의 상태가 북쪽부터 남쪽 순서대로, 각 줄은 서쪽부터 동쪽 순서대로 주어진다. 빈 칸은 0, 벽은 1로 주어진다. 지도의 첫 행, 마지막 행, 첫 열, 마지막 열에 있는 모든 칸은 벽이다.

로봇 청소기가 있는 칸의 상태는 항상 빈 칸이다.

 

[출력]

로봇 청소기가 청소하는 칸의 개수를 출력한다.

 

[예제]


>문제 풀이

 문제가 길고, 조건이 많다. 문제를 읽으면서 로봇 청소기 동작 원리를 대충 파악해야 안헷갈리고 풀 수 있다.

 

<입력>

N, M (배열 사이즈)

r, c (( r , c ):좌표 값)

d (direction: 청소기 방향)

배열 값들이(mat[N][M]) 입력된다.

 

이 문제는 주어진 방향을 기준으로 주변을 탐색해보면서 깊이있게 뻗어나가는 탐색을 한다.

그래서 dfs가 적합할 것이라 생각하여 dfs로 풀었다.

 

dfs(int x, int y, int dir) 함수{

1) 현재 위치 청소

2) 현재 위치와 방향 기준

  1. 왼쪽 방향의 공간이 청소되어있지 않으면?   1)번 부터
  2. 왼쪽 방향의 공간이 청소되어 있으면?   dir를 왼쪽 방향으로 바꾸고 2)번으로
  3. 네 방향 모두 청소되어있거나 벽이면?   dir를 유지한채로 한 칸 후진 후 2)번으로
  4. 네 방향 모두 청소되어있거나 벽이고 && 후진도 못하는 상황??   작동 종료

}

 

각각의 조건에 대한 상황과 원하는 동작 처리를 잘 생각해야한다.

게다가 방향에 대한 조건이 0: 북, 1:동, 2:남, 3:서 로 반시계 방향도 아니기 때문에, 방향을 바꿀 때 처리를 주의해줘야 한다.

 

 

>전체 코드

 

import java.util.*;

public class Main {
	static int N, M, cnt=0;
	static int mat[][], visited[][];
	
	public static void main(String [] args) {
		int r, c, d; //(r, c)좌표, d:방향
		
		Scanner scan= new Scanner(System.in);
		N= scan.nextInt();
		M= scan.nextInt();
		
		r= scan.nextInt();
		c= scan.nextInt();
		d= scan.nextInt(); //0북, 1동, 2남, 3서
		
		mat= new int[N][M];
		visited= new int[N][M];
		
		for(int i=0; i<N; i++) {
			for(int j=0; j<M; j++) {
				mat[i][j]= scan.nextInt();
			}
		}
		
		cnt++;
		dfs(r, c, d);
		
		System.out.println(cnt);
    }//main

	static int[] dx= {-1, 0, 1, 0}; //북, 동, 남, 서
	static int[] dy= {0, 1, 0, -1};
	public static void dfs(int x, int y, int dir) {
		int nx, ny, cant=0;
		
		visited[x][y]=1;
		for(int i=0; i<4; i++) {
			dir= dir-1<0 ? 3:dir-1;
			nx= x+dx[dir];
			ny= y+dy[dir];
			if(nx<0||nx>=N||ny<0||ny>=M) {
			}else if(mat[nx][ny]==0&&visited[nx][ny]==0) {
				dfs(nx, ny, dir);
				cnt++;
				break;
			}
			cant++;
		}
		
		x= x-dx[dir];
		y= y-dy[dir];
		if(x<0||x>=N||y<0||y>=M) {
			return;
		}else if(cant==4&&mat[x][y]==0) {
			dfs(x, y, dir);
		}
	}//dfs
}

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

 

14503번: 로봇 청소기

로봇 청소기가 주어졌을 때, 청소하는 영역의 개수를 구하는 프로그램을 작성하시오. 로봇 청소기가 있는 장소는 N×M 크기의 직사각형으로 나타낼 수 있으며, 1×1크기의 정사각형 칸으로 나누어

www.acmicpc.net

 

[문제]

우리 나라는 가족 혹은 친척들 사이의 관계를 촌수라는 단위로 표현하는 독특한 문화를 가지고 있다. 이러한 촌수는 다음과 같은 방식으로 계산된다. 기본적으로 부모와 자식 사이를 1촌으로 정의하고 이로부터 사람들 간의 촌수를 계산한다. 예를 들면 나와 아버지, 아버지와 할아버지는 각각 1촌으로 나와 할아버지는 2촌이 되고, 아버지 형제들과 할아버지는 1촌, 나와 아버지 형제들과는 3촌이 된다.

여러 사람들에 대한 부모 자식들 간의 관계가 주어졌을 때, 주어진 두 사람의 촌수를 계산하는 프로그램을 작성하시오.

 

[입력]

사람들은 1, 2, 3, …, n (1≤n≤100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어진다. 그리고 셋째 줄에는 부모 자식들 간의 관계의 개수 m이 주어진다. 넷째 줄부터는 부모 자식간의 관계를 나타내는 두 번호 x,y가 각 줄에 나온다. 이때 앞에 나오는 번호 x는 뒤에 나오는 정수 y의 부모 번호를 나타낸다.

각 사람의 부모는 최대 한 명만 주어진다.

 

[출력]

입력에서 요구한 두 사람의 촌수를 나타내는 정수를 출력한다. 어떤 경우에는 두 사람의 친척 관계가 전혀 없어 촌수를 계산할 수 없을 때가 있다. 이때에는 -1을 출력해야 한다.

 

[예제]


>문제 풀이

 촌수로 나타내어져 있지만, 사실 그래프를 그려보면 트리 구조입니다.

그래서 출발지에서 목적지까지 탐색을 통해 몇번 건너야 되는지 구하면 되는 문제였습니다.

입력받은 관계에 따라 트리가 여러개일 수도 있는데, 서로 다른 트리에 위치해 있어서 만날 수 없는 경우에는 -1을 출력해주면 됩니다.

dfs와 bfs 중 어느것을 사용해도 상관없습니다.

 

저는 전체 인원수를 N

출발지와 목적지를 person1, person2라는 뜻에서 p1, p2로

관계의 수를 M

관계에서 받는 변수를 x, y로 받았습니다.

 

>전체 코드

 

import java.util.*;

public class Main {
	static int N, p1, p2, cnt=1;
	static int mat[][], visited[];
	
	public static void main(String [] args) {
		int M, x, y;
		
		Scanner scan= new Scanner(System.in);
		
		N= scan.nextInt();
		mat= new int[N][N];
		visited= new int[N];
		
		p1= scan.nextInt()-1;
		p2= scan.nextInt()-1;
		
		M= scan.nextInt();
		while(M-->0) {
			x= scan.nextInt();
			y= scan.nextInt();
			mat[x-1][y-1]=1;
			mat[y-1][x-1]=1;
		}
		
		for(int i=0; i<N; i++) {
			if(mat[p1][i]==1) {
				visited[p1]=1;
				dfs(p1, i);
			}
		}
		if(visited[p2]!=1) cnt=-1;
        System.out.println(cnt);
    }
    
	public static void dfs(int x, int y) {
		visited[y]=1;
		if(y==p2) {
			cnt=mat[x][y];
			return;
		}
		
		for(int i=0; i<N; i++) {
			if(mat[y][i]>0&&visited[i]!=1) {
				mat[y][i]=mat[x][y]+1;
				mat[i][y]=mat[y][i];
				dfs(y, i);
			}
		}
	}//end dfs
	
}

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

 

2644번: 촌수계산

사람들은 1, 2, 3, …, n (1≤n≤100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어진

www.acmicpc.net

 

[문제]

정사각형으로 이루어져 있는 섬과 바다 지도가 주어진다. 섬의 개수를 세는 프로그램을 작성하시오.

한 정사각형과 가로, 세로 또는 대각선으로 연결되어 있는 사각형은 걸어갈 수 있는 사각형이다. 

두 정사각형이 같은 섬에 있으려면, 한 정사각형에서 다른 정사각형으로 걸어서 갈 수 있는 경로가 있어야 한다. 지도는 바다로 둘러싸여 있으며, 지도 밖으로 나갈 수 없다.

 

[입력]

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 지도의 너비 w와 높이 h가 주어진다. w와 h는 50보다 작거나 같은 양의 정수이다.

둘째 줄부터 h개 줄에는 지도가 주어진다. 1은 땅, 0은 바다이다.

입력의 마지막 줄에는 0이 두 개 주어진다.

 

[출력]

각 테스트 케이스에 대해서, 섬의 개수를 출력한다.

 

[예제]


>문제 풀이

 기존에 4방향(상하좌우)로 뻗어나가면서 탐색했던 것에서 대각선까지 포함해주면 되는 문제

최단거리와 같은 말은 없었기 때문에 DFS로 풀었습니다.

 

>전체 코드

 

import java.util.*;

public class Main {
	static int N, M, cnt;
	static int mat[][], visited[][];
	
	public static void main(String [] args) {
		int p1, p2;
		
		Scanner scan= new Scanner(System.in);
		
		while(true) {
			cnt=0;
			M= scan.nextInt();
			N= scan.nextInt();
			if(M==0&& N==0) break;
			
			mat= new int[N][M];
			visited= new int[N][M];
			
			//입력하기
			for(int i=0; i<N; i++) {
				for(int j=0; j<M; j++) {
					mat[i][j]= scan.nextInt();
				}
			}
			
			for(int i=0; i<N; i++) {
				for(int j=0; j<M; j++) {
					if(mat[i][j]==1&&visited[i][j]!=1) { //1은 땅, 0은 바다
						cnt++;
						dfs(i, j);
					}
				}
			}
			System.out.println(cnt);
		}//while
		
	}//main
	
	public static void dfs(int x, int y) {
		int[] dx= {-1, 0, 1, 0, -1, -1, 1, 1};
		int[] dy= {0, -1, 0, 1, -1, 1, -1, 1};
		int nx, ny;
		
		visited[x][y]=1;
		
		for(int i=0; i<dx.length; i++) {
			nx= x+dx[i];
			ny= y+dy[i];
			if(nx<0||N<=nx||ny<0||M<=ny) continue;
			if(mat[nx][ny]==1&&visited[nx][ny]!=1) {
				dfs(nx, ny);
			}
		}
		
	}//end dfs
}

www.acmicpc.net/problem/4963

 

4963번: 섬의 개수

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 지도의 너비 w와 높이 h가 주어진다. w와 h는 50보다 작거나 같은 양의 정수이다. 둘째 줄부터 h개 줄에는 지도

www.acmicpc.net

 

+ Recent posts