[시간 제한]

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,2,3,4,5 라는 수가 있고, 3번째 수를 6으로 바꾸고 2번째부터 5번째까지 합을 구하라고 한다면 17을 출력하면 되는 것이다. 그리고 그 상태에서 다섯 번째 수를 2로 바꾸고 3번째부터 5번째까지 합을 구하라고 한다면 12가 될 것이다.

 

[입력]

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)과 M(1 ≤ M ≤ 10,000), K(1 ≤ K ≤ 10,000) 가 주어진다. M은 수의 변경이 일어나는 횟수이고, K는 구간의 합을 구하는 횟수이다. 그리고 둘째 줄부터 N+1번째 줄까지 N개의 수가 주어진다. 그리고 N+2번째 줄부터 N+M+K+1번째 줄까지 세 개의 정수 a, b, c가 주어지는데, a가 1인 경우 b(1 ≤ b ≤ N)번째 수를 c로 바꾸고 a가 2인 경우에는 b(1 ≤ b ≤ N)번째 수부터 c(b ≤ c ≤ N)번째 수까지의 합을 구하여 출력하면 된다.

입력으로 주어지는 모든 수는 -263보다 크거나 같고, 263-1보다 작거나 같은 정수이다.

 

[출력]

첫째 줄부터 K줄에 걸쳐 구한 구간의 합을 출력한다. 단, 정답은 -263보다 크거나 같고, 263-1보다 작거나 같은 정수이다.

 

[예제]

 


>문제 풀이

인덱스 트리를 이용하여 풀었습니다.

처음 접하는 알고리즘으로 푸느라 사실 아직 이해를 제대로 했는지 모르겠습니다.

응용 문제가 많다고 하니 개념을 좀 더 살펴 본 후 관련 문제들을 더 풀어보면 좋을 것 같습니다.

인덱스 트리는 구간합을 구해야할 때, 중간에 인덱스의 값이 변하는 상황에서 쓰입니다.

배열로도 구간합을 저장할 수 있지만, 이 경우 특정 인덱스에 담긴 값을 바꿀 경우 배열 전체를 갈아엎어야 합니다..

그래서 이런 경우는 인덱스 트리를 사용하면 되는데, 포화 이진트리의 리프노드에 배열로 받은 값을 넣고 그 위로 구간합을 구하면서 올리는 구조입니다.

처음에 트리를 만들기 위해서는 Bottom-up으로 리프노드부터 위로 올라가는 방법을 쓰는데, 이후 query와(구간합 구하는 메소드) update에서는(특정 인덱스에 담긴 값을 변경) Top-down과 Bottom-up 2가지 방식으로 구현할 수 있습니다.

(알고리즘 수업에서 두 가지 방법으로 모두 구현해 보았지만, 이 문제에서는 top-down으로 제출하였습니다.)

**구현

1. N개의 배열값을 받았다면, 이제 인덱스 트리에 저장해야합니다.

인덱스 트리의 노드 개수는 2의 제곱수-1개 이고 리프 노드에 N개의 값을 저장해야 합니다.

근데 노드 번호는 0부터 시작하는데 연산을 0부터 하도록 구현하기 너무 번거롭습니다. 그래서 node[0]은 남겨두고 1부터 2N까지가 우리가 실제 값을 넣고 사용하는 노드입니다. 즉 트리의 인덱스 N~2N-1까지가 리프노드이고, 여기에 nums[N]을 담아야합니다.

=> N보다 크거나 같은 2의 제곱수가 리프노드의 개수 S이고, 2*S가 트리의 노드 개수 입니다.

ex) N=5 이면 S= 8(=2^3)

2. query ( left, right, node, queryLeft, queryRight)

left: 리프노드의 첫번째 인덱스(=1)

right: 리프노드의 마지막 인덱스(=S)

node: 현재 노드의 번호(Top-down으로 구현했으니 처음에 1로 넘긴다.)

queryLeft: 구하고자하는 구간의 왼쪽 인덱스

queryRight: 구하고자하는 구간의 오른쪽 인덱스

분기점을 3개로 나눠서 구현한다.

(1) (left, right): 현재노드가 담고있는 구간합의 구간과 (queryLeft, queryRight)와 범위가 겹치지 않는 경우

(2) (left, right)와 (queryLeft, queryRight)와 딱 맞는 경우

(3) (left, right)와 (queryLeft, queryRight)이 걸쳐있는 경우

:자식 노드들로 내려가서 필요한 값만 올릴 수 있도록 해야함.

: return query(왼쪽부분) + query(오른쪽 부분)

3. update(left, right, node, target, diff)

: 타겟을 찾아가며 타겟을 포함하는 구간합을 담은 노드에 +diff 처리를 해준다.

: diff 은 c - nums[target]

(c는 업데이트 해야할 값 , nums[target]은 업데이트 전의 값)

 

>전체 코드

 

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


public class Main {
	static long nums[], tree[];
	static int N, M, K, S;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st= new StringTokenizer(br.readLine());
		
		N= Integer.parseInt(st.nextToken());
		M= Integer.parseInt(st.nextToken());
		K= Integer.parseInt(st.nextToken());
		nums= new long[N];
		
		for(int i=0; i<N; i++) {
			nums[i]= Long.parseLong(br.readLine());
		}
		
		S=1;
		while(S<N) {
			S*=2;
		}
		tree= new long[S*2];
		
		initBU();
		
		int a, b;
		long c;
		for(int i=0; i<M+K; i++) {
			st= new StringTokenizer(br.readLine());
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			c= Long.parseLong(st.nextToken());
			
			if(a==1) { //1이면 업뎃
				update(1, S, 1, b, c-nums[b-1]);
				nums[b-1]=c;
			}else if(a==2) { //2면 구간합
				System.out.println(query(1, S, 1, b, c));
			}
		}
		
	}//main
	
	static void initBU() {
		//Leaf에 값 반영
		for(int i=0; i<N; i++) {
			tree[S+i]=nums[i];
		}
		//내부노드 채움
		for(int i=S-1; i>0; i--) {
			tree[i]= tree[i*2]+tree[i*2+1];
		}
	}//init
	
	static long query(int left, int right, int node, int queryLeft, long queryRight) {
		// 연관이 없음
		if(right<queryLeft||queryRight<left) {
			return 0;
		}
		// 현재 노드가 쿼리 범위에 포함됨
		else if(queryLeft<=left&&right<=queryRight) {
			return tree[node];
		}
		// 현재 노드가(정확히는 구간이) 쿼리 범위에 포함은 안되는데, 겹침
		else {
			int mid= (left+right)/2;
			long leftResult= query(left, mid, node*2, queryLeft, queryRight);
			long rightResult= query(mid+1, right, node*2+1, queryLeft, queryRight);
			return leftResult+rightResult;
		}
	}//query
	
	static void update(int left, int right, int node, int target, long diff) {
		//연관없음
		if(target<left||right<target) return;
		//연관 있음
		tree[node]+=diff;
		if(left!=right) {
			int mid= (left+right)/2;
			update(left, mid, node*2, target, diff);
			update(mid+1, right, node*2+1, target, diff);
		}
	}//update
}

[문제]

세계적인 도둑 상덕이는 보석점을 털기로 결심했다.

상덕이가 털 보석점에는 보석이 총 N개 있다. 각 보석은 무게 Mi와 가격 Vi를 가지고 있다. 상덕이는 가방을 K개 가지고 있고, 각 가방에 담을 수 있는 최대 무게는 Ci이다. 가방에는 최대 한 개의 보석만 넣을 수 있다.

상덕이가 훔칠 수 있는 보석의 최대 가격을 구하는 프로그램을 작성하시오.

 

[입력]

첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000)

다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000)

다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci ≤ 100,000,000)

모든 숫자는 양의 정수이다.

 

[출력]

첫째 줄에 상덕이가 훔칠 수 있는 보석 가격의 합의 최댓값을 출력한다.

 

[예제]

[힌트]

두 번째 예제의 경우 첫 번째 보석을 두 번째 가방에, 세 번째 보석을 첫 번째 가방에 넣으면 된다.


>문제 풀이

주어진 보석들의 무게와 값어치를 Gem(weight, price)를 이용하여 Gem[] gem에 저장해줍니다.

우리는 주어진 가방안에 담을 수 있는 무게의 보석들 중에 가장 높은 값어치의 보석을 담아야 합니다.

보석: 처음에는 무게를 기준으로 정렬을 해야하고, 가방에 담을 수 있는 보석들을 대상으로 다시 값이 가장 높은 보석을 구해줍니다.

가방: 무게 기준으로 오름차순 정렬을 해주고 보석을 담아야합니다. 그래야 가방이 담을 수 있는 무게보다 더 적은 무게의 보석들을 고려할 수 있습니다.

ex) 가방= 3, 10 보석: (3, 40) (5, 30) (7, 15) (10, 20) =>이럴 경우 첫번째 가방에 (3, 40)을 담고, 두번째 가방에 (5, 30)을 담아야 합니다.

1. 보석들을 무게 기준으로 오름차순 정렬을 합니다.

(2차원 배열이라 weight과 price 중에 기준을 정해줘야합니다.)

2. 가방을 무게기준으로 오름차순 정렬을 해줍니다.

3. Priority Queue를 선언해줍니다. 이 때 기준을 정해줘야하는데, 값을 기준으로 할 것입니다.

포인터 gIndex=0;

4. 연산

for(i: 가방) {
     while(포인터를 옮겨주며 N보다 작고, gem[gIndex]가 가방의 무게 i보다 작은) {
          조건의 보석들을 pq에 넣어줍니다.
     }
     pq가 비어있지 않다면 result+=pq.poll().price;
}

 

>전체 코드

 

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
	
	public static void main(String[] args) {
		Scanner scan= new Scanner(System.in);
		
		int N= scan.nextInt();
		int K= scan.nextInt();
		Gem[] gem= new Gem[N];
		int[] bags= new int[K];
		
		int w, p;
		for(int i=0; i<N; i++) {
			w= scan.nextInt();
			p= scan.nextInt();
			gem[i]= new Gem(w, p);
		}
		
		for(int i=0; i<K; i++) {
			bags[i]= scan.nextInt();
		}
		
		// 가방 오름차순 정렬
		Arrays.sort(bags);
		// 보석 무게순 정렬(ascending)
		Arrays.sort(gem, (g1, g2)-> Integer.compare(g1.weight, g2.weight));
		// 보석 높은 값 기준 힙
		PriorityQueue<Gem> pq= new PriorityQueue<>((g1, g2)-> Integer.compare(g2.price, g1.price));
		
		int gIndex=0;
		long result=0;
		//1. 남은 가방 중 제일 작은 가방을 선택
		for(int i=0; i<bags.length; i++) {
			//2. 선택된 가방에 넣을 수 있는 남은 보석 중 가장 비싼 보석을 선택(1가방, 1보석)
			while(gIndex<N && gem[gIndex].weight<=bags[i]) {
				pq.add(gem[gIndex++]);
			}
			if(!pq.isEmpty()) result+=pq.poll().price;
		}
		
		System.out.println(result);
		
	}//main
	
	static class Gem {
		int weight;
		int price;
		
		public Gem(int weight, int price) {
			this.weight= weight;
			this.price= price;
		}
	}
}

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

 

1202번: 보석 도둑

첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000) 다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000) 다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci

www.acmicpc.net

 

[문제]

10,000 이하의 자연수로 이루어진 길이 N짜리 수열이 주어진다. 이 수열에서 연속된 수들의 부분합 중에 그 합이 S 이상이 되는 것 중, 가장 짧은 것의 길이를 구하는 프로그램을 작성하시오.

[입력]

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

[출력]

첫째 줄에 구하고자 하는 최소의 길이를 출력한다. 만일 그러한 합을 만드는 것이 불가능하다면 0을 출력하면 된다.

[예제]

 


>문제 풀이

* 투포인터 문제

주어진 N길이의 배열에 대해 연속되는 숫자의 합이 S이상이 되는 것중 가장 짧은 길이를 구해야합니다.

배열에 대해 인덱스를 나타낼 변수 int left=0, right=0를 선언을 해줍니다.

left=0을 기준으로 right를 오른쪽으로 이동해가는데, 이 때 갱신되는 합이 S이상인지 아닌지에 따라 처리를 해줍니다.

오른쪽의 값들을 더해가면 무조건 이전의 sum보다는 sum+arr[right]가 더 큰 값이 됩니다.

위의 조건에 주의하며, while문과 함께 포인터들을 조작하여, 최소 길이인 len을 구해줍니다.

if ( sum>=S )

len와 right-left+1을 비교하여 더 작은 값으로 len을 갱신해줍니다.

//그리고 left변수를 옮겨줄건데, 그럼 sum에서도 빼줘야하기 때문에

sum-= arr[left++];

else //sum<S

//아직 S보다 작은 수이기 때문에 더 더해줘야합니다.

//right변수를 오른쪽으로 옮겨주고 sum에 더해줍니다.

sum+= arr[++right]

if ( left와 right범위 체크)

left와 right를 ++ 해줬을 때 범위를 벗어나면 종료시켜야 합니다.

앞에서 arr[N+1]을 해놨기 때문에 +arr[N] 는 정상 동작합니다. 물론 arr[N]값은 0이기 때문에 더했을 때 sum에 영향을 미치지도 않습니다. 하지만 다음 while문을 반복하기 전 계속 left와 right를 조작해도 되는지 범위 체크를 해줘야합니다.

 

 

>전체 코드

 

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

public class Main {
	
	public static void main(String[] args) throws IOException {
		
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st= new StringTokenizer(br.readLine());
		
		int N, S;
		int arr[];
		
		N= Integer.parseInt(st.nextToken());
		S= Integer.parseInt(st.nextToken());
		arr= new int[N+1];
		
		st= new StringTokenizer(br.readLine());
		for(int i=0; i<N; i++) {
			arr[i]= Integer.parseInt(st.nextToken());
		}
		
		
		int left=0, right=0, sum=arr[0];
		int len=Integer.MAX_VALUE;
		
		while(left<N&&right<N) {
			if(sum<S) {
				sum+=arr[++right];
			}else {
				len= Math.min(len, right-left+1);
				sum-=arr[left++];
			}
			if(left>=N||right>=N) break;
		}
		
		if(len==Integer.MAX_VALUE) len=0;
		System.out.println(len);
	}
}

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

 

1806번: 부분합

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

www.acmicpc.net

 

[문제]

사악한 암흑의 군주 이민혁은 드디어 마법 구슬을 손에 넣었고, 그 능력을 실험해보기 위해 근처의 티떱숲에 홍수를 일으키려고 한다. 이 숲에는 고슴도치가 한 마리 살고 있다. 고슴도치는 제일 친한 친구인 비버의 굴로 가능한 빨리 도망가 홍수를 피하려고 한다.

티떱숲의 지도는 R행 C열로 이루어져 있다. 비어있는 곳은 '.'로 표시되어 있고, 물이 차있는 지역은 '*', 돌은 'X'로 표시되어 있다. 비버의 굴은 'D'로, 고슴도치의 위치는 'S'로 나타내어져 있다.

매 분마다 고슴도치는 현재 있는 칸과 인접한 네 칸 중 하나로 이동할 수 있다. (위, 아래, 오른쪽, 왼쪽) 물도 매 분마다 비어있는 칸으로 확장한다. 물이 있는 칸과 인접해있는 비어있는 칸(적어도 한 변을 공유)은 물이 차게 된다. 물과 고슴도치는 돌을 통과할 수 없다. 또, 고슴도치는 물로 차있는 구역으로 이동할 수 없고, 물도 비버의 소굴로 이동할 수 없다.

티떱숲의 지도가 주어졌을 때, 고슴도치가 안전하게 비버의 굴로 이동하기 위해 필요한 최소 시간을 구하는 프로그램을 작성하시오.

고슴도치는 물이 찰 예정인 칸으로 이동할 수 없다. 즉, 다음 시간에 물이 찰 예정인 칸으로 고슴도치는 이동할 수 없다. 이동할 수 있으면 고슴도치가 물에 빠지기 때문이다.

[입력]

첫째 줄에 50보다 작거나 같은 자연수 R과 C가 주어진다.

다음 R개 줄에는 티떱숲의 지도가 주어지며, 문제에서 설명한 문자만 주어진다. 'D'와 'S'는 하나씩만 주어진다.

[출력]

첫째 줄에 고슴도치가 비버의 굴로 이동할 수 있는 가장 빠른 시간을 출력한다. 만약, 안전하게 비버의 굴로 이동할 수 없다면, "KAKTUS"를 출력한다.

​[예제]

 


>문제 풀이

D : 비버 굴

S : 고슴도치 위치

* : 물

. : 빈 곳

X : 돌

char mat[][]에 입력값들을 저장하면서, 물의 위치와 시작 위치를 큐에 담아줍니다.

물의 위치를 먼저 쭉 담고, 시작 위치를 담도록 구현했는데 물 먼저 bfs를 해주고 고슴도치가 움직여야 정답값을 효율적으로 구할 수 있기 때문입니다.

(고슴도치가 움직여도 그 다음에 물에 잠기면 처리가 좀 더 복잡해집니다.)

물이 상하좌우로 확장해나갈 때는 mat범위 내에서 발생하며 값도 *으로 변경해줍니다.

고슴도치는 빈 곳으로만 이동해서 D에 도달해야합니다. 최종적으로 D에 도달했을 때, 몇 분 걸렸는지를 출력해야하는데 dp[][]값에 시작 위치부터 카운트해서 최단거리로 D에 도달하는 시간을 구했습니다.

위의 과정을 다 돌렸는데도, D에 도달하지 못했다면 "KAKTUS"를 출력합니다.


저는 처음에 고슴도치가 먼저가고 물이 확장되도록 구현했는데, 이렇게 했더니 queue.size()만큼만 for문을 반복하게 하는데에서 어려움을 겪었습니다.

 

https://arinnh.tistory.com/40?category=934581 

 

[프로그래머스] level 2: 가장 먼 노드_Java

[문제 설명] n개의 노드가 있는 그래프가 있습니다. 각 노드는 1부터 n까지 번호가 적혀있습니다. 1번 노드에서 가장 멀리 떨어진 노드의 갯수를 구하려고 합니다. 가장 멀리 떨어진 노드란 최단

arinnh.tistory.com

이 문제를 풀었던 것처럼 풀어보려고 했는데, 잘 안되어서 코드를 수정했습니다.

 

 

>전체 코드

 

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
	static int R, C;
	static char[][] mat;
	static int[][] dp;
	static Queue<Point> queue;
	
	static int time=0;
	
	public static void main(String[] args) {
		Point start = null;
		String str="";
		
		Scanner scan= new Scanner(System.in);
		
		R= scan.nextInt();
		C= scan.nextInt();
		mat= new char[R][C];
		dp= new int[R][C];
		queue= new LinkedList<>();
		
		//빈 곳: . // 물: * // 돌: X //비버 굴: D // 고슴도치: S
		for(int i=0; i<R; i++) {
			str= scan.next();
			for(int j=0; j<C; j++) {
				mat[i][j]= str.charAt(j);
				if(mat[i][j]=='S') {
					start= new Point(i, j, 'S');
				}else if(mat[i][j]=='*') {
					queue.add(new Point(i, j, '*'));
				}
			}
		}
		queue.add(start);
		
		bfs();
	}//main
	
	
	static int[] dx= {-1, 0, 1, 0};
	static int[] dy= {0, -1, 0, 1};
	
	//고슴도치가 비버 굴로 가야함(순서:물->고슴도치)
	public static void bfs() {
		Point p;
		int nx, ny;
		
		while(!queue.isEmpty()) {
			p= queue.poll();
			if(p.type=='D') {
				System.out.println(dp[p.x][p.y]);
				return;
			}

			for(int i=0; i<4; i++) {
				nx= p.x+dx[i];
				ny= p.y+dy[i];

				if(!check(nx, ny)) continue;				
				if(p.type=='*') {
					if(mat[nx][ny]=='.'||mat[nx][ny]=='S') {
					mat[nx][ny]= '*';
					queue.add(new Point(nx, ny, '*'));
					}
				}else { //p.type=='.'
					if(mat[nx][ny]=='.'||mat[nx][ny]=='D') {
						if(dp[nx][ny]>0) continue;
						dp[nx][ny]= dp[p.x][p.y]+1;
						queue.add(new Point(nx, ny, mat[nx][ny]));
					}
				}
			}
		}//while
		System.out.println("KAKTUS");
	}

	//범위 확인
	public static boolean check(int x, int y) {
		if(x<0||x>=R) return false;
		if(y<0||y>=C) return false;
		return true;
	}
	
	static class Point{
		int x, y;
		char type;
		public Point(int x, int y, char type) {
			this.x= x;
			this.y= y;
			this.type= type;
		}
	}
}

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

 

3055번: 탈출

사악한 암흑의 군주 이민혁은 드디어 마법 구슬을 손에 넣었고, 그 능력을 실험해보기 위해 근처의 티떱숲에 홍수를 일으키려고 한다. 이 숲에는 고슴도치가 한 마리 살고 있다. 고슴도치는 제

www.acmicpc.net

 

+ Recent posts