Goal

  • Segment Tree에 대해 초심자에게도 설명할 수 있도록
  • Segment Tree 장, 단점 파악
  • Segment Tree 구현

 

 

Abstract

세그먼트 트리(Segment Tree)는 주어진 배열에 대한 구간 쿼리를 효과적으로 수행하기 위한 자료 구조 입니다. 주로 배열의 구간 합, 최소값, 최대값 등을 빠르게 계산하는 데 사용됩니다. 이 구조는 특히 배열이 자주 변경되는 경우에도 효과적입니다. 

 

세그먼트 트리는 트리 구조이며, 각 노드는 배열의 일부 구간에 대한 정보를 저장합니다. 각 노드는 자식 노드들의 정보를 조합하여 부모 노드에 저장된 정보를 갱신합니다. 트리 구조이기때문에 이러한 갱신의 시간 복잡도는 O(logN) 입니다.

 

 

 

Why Learn?

보통 많은 알고리즘 문제들은 10초 이내에 작업이 수행되도록 문제를 내곤합니다. 만약, 배열의 크기인 N값이 10만이고 구간에 대한 변경 이 꾸준하게 있으며 구간 합 or 최소값 or 최대값 을 요구하는 문제의 경우 트리 구조를 사용하지 않는다면 시간복잡도는 O(N^2)인 10만 x 10만으로 1초에 1억번 연산을 한다는 가정하에 100초 정도의 시간이 걸리게 됩니다. 

 

이는 문제에서 요구하는 시간초를 넘어가기 때문에 효율성 부분에서 감점이 될 것 입니다. 이러한 문제에서 필요한 알고리즘 바로 세그먼트 트리입니다. 세그먼트 트리는 해당 문제를 O(NlogN)으로 문제를 해결하기 때문에 0.005초가 걸리게됩니다.

 

N값이 10만인 경우 100초와 0.005초의 차이가 납니다. 이는 N값이 커질 경우 더 큰 시간 차이를 보입니다. 그렇기때문에 꼭 배워야하는 알고리즘입니다.

 

 

 

Process

  1. leaf 노드부터 문제에 의도에 맞는 값을 부모노드로 최신화 시키며 트리를 구성합니다. (합, 최대값, 최소값)
  2. 최신화할 값이 있다면 해당 노드부터 루트노드까지 최신화 시켜줍니다.
  3. 합, 최대값, 최소값을 구하는 구간만큼 트리에서 가져옵니다.
  4. 2~3 번을 반복합니다.

 

 

Code (Java)

// 구간합을 최신화하며 값을 구해오는 구조
class SegmentTree{
        long tree[];	// 1.
        int treeSize;

	// 2.
        public SegmentTree(int arrSize){
            int h = (int) Math.ceil(Math.log(arrSize)/ Math.log(2));

            this.treeSize = (int) Math.pow(2,h+1);
            tree = new long[treeSize];
        }
		
        // 3.
        // node: 현재 노드 위치
        // start: 시작 인덱스, end: 끝 인덱스
        public long init(long[] arr, int node, int start,int end){
            
            if(start == end){
                return tree[node] = arr[start];
            }

            return tree[node] =
            init(arr,node*2,start,(start+ end)/2)
            + init(arr,node*2+1,(start+end)/2+1,end);
        }
		
        // 4.
        // node: 현재 노드 위치
        // start: 시작 인덱스, end: 끝 인덱스
        // idx: 구간 합을 수정하고자 하는 노드
        // dif: 수정할 값
        public void update(int node, int start, int end, int idx, long diff){
            if(idx < start || end < idx) return;

            tree[node] += diff;

            if(start != end){
                update(node*2, start, (start+end)/2, idx, diff);
                update(node*2+1, (start+end)/2+1, end, idx, diff);
            }
        }
		
        // 5.
        // node: 현재 노드 위치
        // start: 시작 인덱스, end: 끝 인덱스
        // left, right: 구간 합을 구하고자 하는 범위
        public long sum(int node, int start, int end, int left, int right){
            if(left > end || right < start){
                return 0;
            }

            if(left <= start && end <= right){
                return tree[node];
            }

            return sum(node*2, start, (start+end)/2, left, right)+
                    sum(node*2+1, (start+end)/2+1, end, left, right);
        }
    }
}
  1. 트리 구조에서 값을 최신화시킬 배열이다.
  2. 트리를 적당한 크기로 생성하는 생성자이다. 2^h >= arrSize인 h 값을 찾아야 한다. log를 씌우고 +1 한값으로 배열의 크기를 정한다. (arrSize * 4)로 해도 무방하다. 
  3. 재귀 형식이다. start=end이면 leaf 노드로 값을 채우고 아니라면 자식 노드에서 더한 값을 node에 최신화 시켜준다.
  4. 모든 노드를 돌며, idx에 해당되는 node만 값을 수정해준다.
  5. left와 right에 포함되는 노드의 값만 return 해주며 재귀형식으로 더해서 RETURN 해준다.

 

 

그림으로 이해하는 Segment Tree

이해를 위해서 그림은 링크를 참고하여 가져왔습니다.

 

init 함수

재귀 방식으로 leaf노드까지 간 뒤, 해당 노드의 값을 초기화 시키고 값을 return 해줍니다. return 된 자식 노드의 합을 현재 노드의 값으로 초기화 시켜주면서 root 노드까지 값이 초기화됩니다.

 

init 함수가 종료되면 아래와 같은 트리 형식을 배열로 값을 가지고 있다고 보면됩니다.

 

 

update 함수

만약, 4번째 값을 2에서 10으로 바꾼다고 가정합시다. 그렇다면 update 함수에서는 트리의 값을 어떻게 초기화 시킬까요?

위와 같은 작업이 이루어 집니다. idx가 start와 end 사이에 있다면 10-2만큼 값을 더해줍니다. 0~11, 0~5, 0~2(X), 3~5, 3~4, 3(X), 4, 5(X), 6~11(X) X표시 친 부분에서 함수 맨 위 조건문으로 return 되며 X표시 치지 않은 곳들은 값이 더해집니다.

 

 

sum 함수

만약, 8~11 값의 합을 구하는 상황이라면 sum 함수는 어떻게 동작할까요?

 

위와 같은 방식으로 동작합니다. 8~11인덱스에 포함되지 않은 구역은 0을 return, 8~11에 완전히 포함되어 있다면 현재 노드 값 Return, 완전히 포함되어 있디 않다면 포함된 구역만 찾기 위해 자식 노드를 탐색하여 더한 값을 return 합니다.

 

 

시간복잡도

N값이 배열의 크기고, K번 만큼의 update, sum 작업이 있다고 가정하면 O(KlogN)의 시간복잡도를 가질 것이다.

 

 

 

공간복잡도

N만큼의 int 혹은 long의 배열로 문제를 풀어내는 것보다는 많은 공간을 차지한다. 보통 N*4로 트리 배열의 크기를 사용한다고 가정한다면, N*3의 자료형만큼 더 많은 공간을 사용하게 된다.

 

 

 

장점

  • 시간이 훨씬 빠르다. 

 

 

단점

  • 구현이 복잡하다.
  • 더 많은 공간을 사용한다.

 

 

결론

더 많은 공간을 차지하긴 하지만 시간복잡도에서 많은 우위를 점할 수 있는 알고리즘이다. 적절하게 사용하면 유용한 알고리즘이다.

비트마스킹

비트마스크라고도 한다. 비트마스킹에 대한 정의가 없는 것 같은데 비트와 마스크를 따로 보고 생각하면 비트는 우리가 흔히 생각하는 0과 1로 표현하는 2진수이다. 마스크는 기관지인 입과 코를 보호하기 위해 덮는 수단?이라고 생각한다. 다른 방식으로 보면 무언가를 덮는 용도라고 볼 수도 있다. 따라서, 우리가 생각하는 정수형을 bit로 덮어서(마스크의 역할) 표현하는 것을 비트마스크 및 비트마스킹이라고 생각한다.

 

 

왜 사용해야 해?

 

첫 번째, 메모리가 효율적이다.

비트마스크는 x번째가 체크가 되어있는지? 또는 포함이 되어있는지 확인해야할 때 쓰면 메모리적으로 많은 효율을 볼 수 있다고 생각한다. 

 

만약, a0123456789z 이라는 문자열이 있고 a에서 시작하여 z까지 가는데 지나친 0-9까지의 숫자를 모두 출력하라는 문제가 있다. 문자열을 0부터 시작하여 돌면서 그냥 출력할 수도 있지만 list에 담아서 출력한다고 가정해보자. 이때, 드는 메모리는 int형인 4byte가 총 10개로 40byte가 사용된다. 해당 문제를 비트마스킹으로 사용하면 int형 1개인 4byte로 표현할 수 있다.

 

이처럼 이렇게 간단한 문제에서도 4byte와 40byte를 사용하는 것에서 차이를 얻을 수 있다. 큰 프로젝트라면 상상할 수 없는 차이를 낼 수 있을 것이다.

 

 

두 번째, 연산이 빠르다.

앞서 문제를 조금만 바꿔서 예시를 들어보자. a부터 z안에 0-9까지의 숫자가 적힌 문자열이 아래와 같이 여러개가 있다. 이때, 0-9중 2개의 숫자를 골라서 아래 문자열 중 값을 모두 표현할 수 있는 문자열의 개수를 출력하라는 문제가 있다고 가정해보자.

a4398z
a23z
a32z
a22z

그렇다면 문자열에 있는 숫자인 2,3,4,8,9 중에서 2개의 모든 조합으로 표현할 수 있는 문자열을 확인하고 그 중 최대값을 출력하면 될 것이다. 이때, 2개의 조합을 HashSet에 넣어 확인한다고 가정하자. 2,3부터 첫번째 문자열에서 4를 확인하고 3을 확인하고 9를 확인하고 8을 확인하여 첫번째 문자열에서 4번을 확인한다. 2개의 조합으로 4개의 문자열을 확인하면 총 10번을 확인해야 한다. 이것을 2,3,4,8,9에서 2개의 모든 조합을 확인한다고 하면 20번이고 여기서 10번을 곱하면 200번이 될 것이다. 하지만, 여기서 비트마스킹을 사용한다면 문자열 별로 1번씩만 확인하면 된다. 2개의 조합으로 문자열의 개수인 4번 여기서 20번을 곱하면 결과적으로 총 80번을 확인하면 된다. 해당 문제에서 200번과 80번의 차이가 발생한다. 이는 문자열에 숫자의 조합이 많으면 많을 수록 비트마스킹의 효과가 극대화될 것이다. 

 

이처럼 문제에 따라서 더 빠른 연산을 얻을 수도 있다.

 

 

그렇다면, 사용하지 말아야할 이유는 없을까?

 

첫 번째, 연산이 느리다.

의아할 수 있다. 앞서 사용해야하는 이유 중 하나가 연산이 빠르다고 했기 때문이다. 하지만, 문제에 따라서 연산이 더 느려질 수도 있다는 것을 인지해야 한다. 즉, 문제에 따라서 비트마스킹은 장점이 될 수도 단점이 될 수도 있다. 그렇기때문에 잘 이해하고 사용해야 한다. 

 

앞서 말한 사용해야할 이유의 첫 번째 예시를 생각해보자. a0123456789z 이라는 문자열이 있고 a에서 시작하여 z까지 가는데 지나친 0-9까지의 숫자를 모두 출력하라는 문제였다. 여기서 비트마스킹을 사용하면 출력 하기 전 if문을 한번 거쳐야 하고 0-9까지 모든 수를 확인해야한다. 하지만, list를 사용하는 경우 if문을 거치지 않고 0-9까지 모든 수를 확인할 필요도 없이 list에 있는 값들만 출력하면 된다. 물론, 값이 겹치지 않다는 가정하의 얘기이다. 이처럼 문제에 따라서 이점을 얻을 수 있다는 것을 생각하자.

 

두 번째, 표현값 제한이 있다. 

표현할 수 있는 가장 큰값은 32bit로 0111 1111 1111 1111 1111 1111 1111 1111(2) = 2147483647이다. 따라서 총 31개의 값까지만 체크할 수 있다고 생각하면 된다. 

 

 

어떻게 사용할까?

일단, 연산자에 대한 이해가 있어야 한다. &(and), |(or), ^(xor), ~(not), <<>>(shift)

 

예시를 들어보자. 아래와 같이 a,b 2개의 2진수가 있다.

a - 1010
b - 1100

a&b -> 1000

a|b -> 1110

a^b -> 1001

~a -> 0101

a<<1 -> 0100

 

아래는 비트를 통하여 문제를 해결해나갈 때 알아두면 좋은 방법들을 잘 정리하였길래 가져왔다. 참고하면 좋을 것 같다. 

 

 

비트마스킹 문제

위의 정리한 내용들은 아래 비트마스킹의 여러 문제들을 풀어보며 느낀점들이다. 아래 문제들을 추가할테니 풀어보고 싶다면 풀어보길 바란다.

 

참고 문서

 

 

11286번: 절댓값 힙

첫째 줄에 연산의 개수 N(1≤N≤100,000)이 주어진다. 다음 N개의 줄에는 연산에 대한 정보를 나타내는 정수 x가 주어진다. 만약 x가 0이 아니라면 배열에 x라는 값을 넣는(추가하는) 연산이고, x가 0

www.acmicpc.net

 

 

 

  • 풀이

 

Comparable 인터페이스를 이용해 클래스 데이터 문제의 조건에 맞게 정렬하면서 넣어준 뒤, 빼는 식의 풀이를 사용했습니다.

 

 

 

  • 코드

 

import java.io.*;
import java.util.*;

public class Main {
	static StringBuilder sb;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(sb);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		int N = in.nextInt();
		sb = new StringBuilder();
		PriorityQueue<Node> pq = new PriorityQueue<>();

		while(N-->0) {
			int input = in.nextInt();
			if(input == 0) {
				if(pq.size() == 0) {
					// 큐가 비어있다면 0 출력
					sb.append("0").append("\n");
				} else {
					// 큐가 비어있지 않다면 큐에 저장한 값 중 가장 작은 값 출력
					sb.append(pq.poll().value).append("\n");
				}
				continue;
			}

			// 입력이 0이 아니라면 큐에 입력값과 절댓값 추가
			int absoluteInput = Math.abs(input);
			pq.add(new Node(input, absoluteInput));
		}
	}
}

class Node implements Comparable<Node> {
	int value;
	int absoluteValue;

	public Node(int value, int absoluteValue) {
		this.value = value;
		this.absoluteValue = absoluteValue;
	}

	@Override
	public int compareTo(Node o) {
		// TODO Auto-generated method stub
		// 절댓값 값이 같다면 입력값이 가장 작은 값
		if(this.absoluteValue == o.absoluteValue) {
			return this.value - o.value;
		} else {
			// 절댓값이 다르다면 절댓값으로 오름차순 정렬
			return this.absoluteValue - o.absoluteValue;
		}
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

2961번: 도영이가 만든 맛있는 음식

첫째 줄에 재료의 개수 N(1 ≤ N ≤ 10)이 주어진다. 다음 N개 줄에는 그 재료의 신맛과 쓴맛이 공백으로 구분되어 주어진다. 모든 재료를 사용해서 요리를 만들었을 때, 그 요리의 신맛과 쓴맛은

www.acmicpc.net

 

 

 


 

 

 

  • 풀이

 

문제는 신맛, 쓴맛의 재료만 있고 신맛과 쓴맛의 조합은 최대 10가지가 있습니다.

조합을 할때마다 신맛끼리는 곱해지고, 쓴맛끼리는 더해집니다.

적절히 조합하여 신맛과 쓴맛의 절대값차이가 가장 적은 수를 구하면 되는 문제입니다.

 

일단 조합은 최대 10가지로 모든 경우의 수를 확인하는 완탐방식을 생각했습니다. 시간은 1초이고, 10!의 시간으로 완전탐색은 1초안에 될 것이라고 생각했습니다.

 

참고로 이 문제는 비트마스킹으로 조금 더 쉽게 구현할 수 있습니다.

 

비트마스킹의 방법은 1 << N을 함으로써 2^N번을 바깥 for문을 통해서 확인합니다.

0000

0001

0010

0011

0100

0101

....

1111

이런식으로 N개의 수 중 택한 경우는 1, 택하지않은 경우는 0으로 모든 경우의 수를 확인할 수 있게합니다.

안쪽 for문에서는 0~3까지의 수를 반복함으로써 바깥 for문의 1의 자리, 2의 자리, 3의 자리, 4의 자리의 숫자가 1인지 0인지 확인하고, 1이면 신맛, 쓴맛 추가 0이면 작없을 하지 않는 방법으로 진행합니다.

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N;
	static long answer;
	static boolean[] check;
	static ArrayList<Flavor> flavor;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		check = new boolean[N];
		answer = Integer.MAX_VALUE;
		flavor = new ArrayList<>();
		for (int i = 0; i < N; i++)
			flavor.add(new Flavor(in.nextInt(), in.nextInt()));
		dfs(0);
	}

	private static void dfs(int index) {
		if (index == N) {
			int sour = 1;
			int bitter = 0;
			int count = 0;
			for (int i = 0; i < check.length; i++) {
				if (!check[i])	continue;
				count++;
				sour *= flavor.get(i).sour;
				bitter += flavor.get(i).bitter;
			}
			if (count == 0)
				return; 
			
			if (answer > Math.abs(sour - bitter))
				answer = Math.abs(sour - bitter);
			return;
		}
		
		check[index] = true;
		dfs(index + 1);
		check[index] = false;
		dfs(index + 1);
	}
}

class Flavor {
	int sour;
	int bitter;

	public Flavor(int sour, int bitter) {
		this.sour = sour;
		this.bitter = bitter;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

 

  • 비트마스킹 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N;
	static long answer;
	static boolean[] check;
	static ArrayList<Flavor> flavor;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		check = new boolean[N];
		answer = Integer.MAX_VALUE;
		flavor = new ArrayList<>();
		for (int i = 0; i < N; i++)
			flavor.add(new Flavor(in.nextInt(), in.nextInt()));
		
		for(int i = 1; i < 1 << N; i++) {
			int sour = 1, bitter = 0;
			for(int j = 0; j < N; j++) {
				if((i & 1<<j) > 0) {
					sour *= flavor.get(j).sour;
					bitter += flavor.get(j).bitter;
				}
			}
			answer = Math.min(answer, Math.abs(sour-bitter));
		}
	}
}

class Flavor {
	int sour;
	int bitter;

	public Flavor(int sour, int bitter) {
		this.sour = sour;
		this.bitter = bitter;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

7569번: 토마토

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N과 쌓아올려지는 상자의 수를 나타내는 H가 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M ≤ 100, 2 ≤ N ≤ 100,

www.acmicpc.net

 

 

 


 

 

 

  • 풀이

 

문제를 간단히 말하자면 3차원의 NxMxH칸에 토마토가 있을수도, 없을수도 있습니다. 또한 토마토는 익은 토마토 or 익지 않은 토마토가 있습니다.

 

1초가 지날때마다 익은 토마토주변 2차원 기준 상하좌우, 3차원 기준 위칸과 아래칸에 익지 않은 토마토를 익게해줍니다.

 

토마토가 모두 익을 때 걸리는 시간 or 토마토를 모두 익히지 못한다면 -1을 출력하면 되는 문제입니다.

 

 

가장 먼저 떠오른 해결책은 너비우선탐색 이였습니다. 익은 토마토의 위치를 queue에 넣어준 뒤, 1초마다 넣어둔 익은 토마토의 위치를 꺼내서 상하좌우, 위아래칸을 탐색하면 된다고 생각했습니다.

 

기존 너비우선탐색 문제들은 2차원에서 탐색하는 문제였는데 해다아 문제는 3차원에서 너비우선탐색을 해야되는 문제입니다. 하지만 어렵지 않습니다. 기존 탐색은 인덱스별로 4번을 한다고한다면 3차원은 위,아래만 추가되기 때문에 탐색을 6번을 하기만하면 되기 때문입니다.

 

이를 기준으로 아래의 코드를 작성하였습니다.

 

만약 4번의 너비우선탐색을 처음 보는 분들은 다른 2차원의 너비우선탐색의 문제들을 풀어본다음에 풀어보시면 좋을 것 같습니다.

 

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, M, H, answer;
	static int[][][] array;
	static boolean[][][] check;
	static Queue<Node> queue;
	static int[] dx = {-1,1,0,0,0,0};
	static int[] dy = {0,0,-1,1,0,0};
	static int[] dh = {0,0,0,0,-1,1};

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		M = in.nextInt();
		N = in.nextInt();
		H = in.nextInt();
		array = new int[H][N][M];
		check = new boolean[H][N][M];
		queue = new LinkedList<>();
		for(int i = 0 ; i < H; i++) {
			for(int j = 0; j < N; j++) {
				for(int z = 0; z < M; z++) {
					array[i][j][z] = in.nextInt();
					if(array[i][j][z] == 1) {
						queue.add(new Node(j,z,i));
						check[i][j][z] = true;
					}
				}
			}
		}
		bfs();
		for(int i = 0 ; i < H; i++) {
			for(int j = 0; j < N; j++) {
				for(int z = 0; z < M; z++) {
					if(array[i][j][z] == 0) { 
						answer = -1;
						break;
					}
				}
			}
		}
		if(answer != -1) answer--;
	}
	
	public static void bfs() {
		while(!queue.isEmpty()) {
			int size = queue.size();
			
			while(size-->0) {
				Node node = queue.poll();
				for(int direction = 0; direction < 6; direction++) {
					int r = node.x + dx[direction];
					int c = node.y + dy[direction];
					int h = node.h + dh[direction];
					
					if(r < 0 || c < 0 || h < 0 || r >= N || c >= M || h >= H) continue;
					if(array[h][r][c] != 0 || check[h][r][c]) continue;
					
					check[h][r][c] = true;
					array[h][r][c] = 1;
					queue.add(new Node(r,c,h));
				}
			}
			answer++;
		}
	}
}

class Node {
	int x;
	int y;
	int h;
	
	public Node(int x, int y, int h) {
		this.x = x;
		this.y = y;
		this.h = h;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

 

 

 

7453번: 합이 0인 네 정수

첫째 줄에 배열의 크기 n (1 ≤ n ≤ 4000)이 주어진다. 다음 n개 줄에는 A, B, C, D에 포함되는 정수가 공백으로 구분되어져서 주어진다. 배열에 들어있는 정수의 절댓값은 최대 228이다.

www.acmicpc.net

 

 


 

 

  • 풀이

 

N개의 배열 크기를 갖고있는 4개의 배열의 값을 다 더했을 때 0을 만들 수 있는 개수를 찾는 문제입니다.

 

처음에 시간이 12초로 넉넉하기 때문에 완전탐색을 사용했습니다. 4000*3999*3998*3997의 시간복잡도로 11억 9천..으로 12초 정도 걸릴 것으로 예상하고 풀어봤지만, 시간 초과로 통과되지 않았습니다.

 

두번째론 HashMap을 사용해서 1/2번째 배열을 모두 더한 값, 3/4번째 배열을 모두 더한 값을 HashMap에 넣은 뒤 두 HashMap을 더했을 때 0이되는 수를 찾아서 해결해봤지만 시간 초과로 통과되지 않았습니다. (O(N^2))라고 생각했는데 왜 안되는지... ==> 찾아보니 해시 충돌 시 O(N)이 되서 시간 초과가 난 것 같습니다.

 

세번째로 upperbound, lowerbound로 풀었습니다. 값을 찾을 시 가장 오른쪽의 인덱스와 가장 왼쪽의 인덱스를 통해 찾는 값이 몇개인지 이분 탐색을 통해 찾고 인덱스의 차를 통해 개수를 더해주는 방식입니다. 기존 해시 충돌시 O(N)의 시간복잡도가 나오는 hashmap대신 이분탐색으로 O(log n)으로 풀어보려했습니다. 하지만 해당 방식도 시간 초과로 통과되지 않았습니다.

 

네번째로 투포인터를 사용해서 풀었습니다. AB를 더한 배열은 왼쪽 index부터, CD를 더한 배열은 오른쪽 index부터 안쪽으로 가며 AB[왼쪽index]+CD[오른쪽index]가 0일시 같은 값이 몇개인지 구해주고 곱해서 개수를 더해줍니다. 이때, count를 세는 자료형을 long으로 해주어야합니다. count는 최대 16000000인데, count가 두개이므로 16000000*16000000으로

256000000000000가 되어 int 범위를 초과하기 때문입니다.

 

 

 

  • 성공 코드

 

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N;
	static long answer;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		answer = 0;
		int[][] array = new int[N][4];
		int[] AB = new int[N * N];
		int[] CD = new int[N * N];

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < 4; j++) {
				array[i][j] = in.nextInt();
			}
		}

		int index = 0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				AB[index] = array[i][0] + array[j][1];
				CD[index] = array[i][2] + array[j][3];
				index++;
			}
		}

		Arrays.sort(AB);
		Arrays.sort(CD);
		
		twoPoint(AB, CD);
	}

	private static void twoPoint(int[] AB, int[] CD) {
		int left = 0;
		int right = N*N-1;
		
		while(left<N*N && right >-1) {
			int valueOfAB = AB[left];
			int valueOfCD = CD[right];
			int sum = valueOfAB+valueOfCD;
			
			if(sum<0) {
				left++;
			}else if(sum>0){
				right--;
			}else {
				long count1 = 0, count2 = 0;
				while(left<N*N && valueOfAB==AB[left]) {
					left++;
					count1++;
				}
				while(right>-1 &&valueOfCD==CD[right]) {
					right--;
					count2++;
				}
				answer+= count1*count2;
			}
		}
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

 

  • 실패 코드

dfs

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, answer;
	static int[][] array;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		N = in.nextInt();
		answer = 0;
		array = new int[N][4];
		
		for(int i = 0; i < N; i++) {
			for(int j = 0; j < 4; j++) {
				array[i][j] = in.nextInt();
			}
		}
		
		dfs(0, 0);
	}
	
	private static void dfs(int index, int sum) {
		if(index == 4) {
			if(sum == 0) answer++;
			return;
		}
		
		for(int i = 0 ; i < N; i++) {
			dfs(index+1, sum+array[i][index]);
		}
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}


HashMap

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, answer;
	static int[][] array;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		N = in.nextInt();
		answer = 0;
		array = new int[N][4];
		
		for(int i = 0; i < N; i++) {
			for(int j = 0; j < 4; j++) {
				array[i][j] = in.nextInt();
			}
		}
		
		Map<Integer, Integer> map1 = new HashMap<>();
		Map<Integer, Integer> map2 = new HashMap<>();
		
		for(int i = 0; i < N; i++) {
			for(int j = 0 ; j< N; j++) {
				int value1 = array[i][0]+array[j][1];
				int value2 = array[i][2]+array[j][3];
				
				if(map1.containsKey(value1)) {
					map1.put(value1, map1.get(value1)+1);
				} else {
					map1.put(value1, 1);
				}
				
				if(map2.containsKey(value2)) {
					map2.put(value2, map2.get(value2)+1);
				} else {
					map2.put(value2, 1);
				}
			}
		}
		
		map1.forEach((key, value) ->{
			if(map2.containsKey(key*-1)) {
				answer += value*map2.get(key*-1);
			}
		});
		
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

upperbound / lowerbound

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, answer;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		answer = 0;
		int[][] array = new int[N][4];
		int[] AB = new int[N * N];
		int[] CD = new int[N * N];

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < 4; j++) {
				array[i][j] = in.nextInt();
			}
		}

		int index = 0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				AB[index] = array[i][0] + array[j][1];
				CD[index] = array[i][2] + array[j][3];
				index++;
			}
		}

		Arrays.sort(CD);
		
		for (int key : AB) {
			int upper = upperBound(CD, key * -1);
			int lower = lowerBound(CD, key * -1);
			answer += (upper - lower);
		}
	}

	private static int upperBound(int[] array, int find) {
		int left = 0;
		int right = array.length;
		while (left <= right) {
			int mid = (left + right) / 2;
			if (array[mid] <= find) {
				left = mid + 1;
			} else {
				right = mid - 1;
			}
		}
		return left;
	}

	private static int lowerBound(int[] array, int find) {
		int left = 0;
		int right = array.length;
		while (left <= right) {
			int mid = (left + right) / 2;
			if (array[mid] < find) {
				left = mid + 1;
			} else {
				right = mid - 1;
			}
		}
		return left;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

1561번: 놀이 공원

첫째 줄에 N(1 ≤ N ≤ 2,000,000,000)과 M(1 ≤ M ≤ 10,000)이 빈칸을 사이에 두고 주어진다. 둘째 줄에는 각 놀이기구의 운행 시간을 나타내는 M개의 자연수가 순서대로 주어진다. 운행 시간은 1 이상 30

www.acmicpc.net

 

 

 


 

 

 

  • 풀이

 

 

놀이공원에서 M개의 놀이기구를 타기위해 N명의 사람들이 기다리고있다. N명의 사람들은 운행중이지 않은 놀이기구 중 가장 앞 번호의 놀이기구를 탄다고 했을 때, 맨 마지막인 N번째 사람은 M개중 몇번째 놀이기구를 탈 것인가?? 를 묻는 문제입니다.

 

일단, N의 값이 20억이기 때문에 1명, 1명 몇번째 놀이기구를 타는지를 계산하면 적어도 1초에 1억번 계산한다 했을 때 20초가 걸린다고 생각하여 시간 초과가 뜰 것이라고 생각했습니다.

 

시간을 줄이기 위해 다른 방법을 생각해야만 했는데, 사람을 세는 것이 아닌 몇 초 후가 지났을 때 몇명이 탔는가에 대해 초점을 맞추면 이분 탐색이 가능합니다. 이분 탐색은 시간복잡도가 O(log N)으로 매우 빠르기때문에 시간안에 들어올거라 생각했습니다.

 

이분 탐색의 풀이 과정은 아래와 같습니다.

  1. 이분 탐색을 통해 마지막 사람이 타는 시간이 몇초 후인지 알아냅니다.
  2. 이분 탐색을 통해 얻어온 시간 전에 몇명의 사람이 탔는지 구합니다. (예를들어, 10초 후에 N번째  사람이 탄다고 가정하면 9초 후에 몇명의 사람이 탔는지 더해놓습니다.)
  3. 반복문을 통해 N 번째 사람이 타는 시간에 N번째 전 사람들이 타는 횟수를 더해줍니다. 더하다가 N번째 사람이 탑승하면 해당 놀이기구 번호를 저장해주고 반복문을 종료합니다. 

 

참고

right 값을 선언할 때 long으로 타입변환을 시키지 않으면 제대로된 값을 구해오지 못합니다.

N을 int, M을 int로 받아왔을 때 N값이 20억, M값이 1, 놀이기구 시간이 2일 경우, N/M*(놀이기구 중 최대 시간)을 하면 40억으로 int형으로는 다 표현할 수 없기 때문입니다. 

 


 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, M, answer;
	static int[] array;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		M = in.nextInt();
		answer = 0;
		array = new int[M+1];
		int max = 0;
		
		if(N <= M) {
			answer = N;
			return;
		}
		
		
		for(int i = 1; i <= M; i++) {
			array[i] = in.nextInt();
			max = Math.max(max, array[i]);
		}
		
		long high = binarySearch(max);
		
		long sum = M;
		for (int i = 1; i <= M; i++)
			sum += (high - 1) / array[i];

		for (int i = 1; i <= M; i++) {
			if (high % array[i] == 0)
				sum++;

			if (sum == N) {
				answer = i;
				break;
			}
		}
	}
	
	private static long binarySearch(int max) {
		long left = 0;
		long right = (long) N/M*max;
		
		while(left <= right) {
			long mid = (left + right) / 2;
			
			long sum = M;
			for(int i = 1; i <= M; i++) {
				sum += mid/array[i];
			}
			
			if(sum < N)
				left = mid +1;
			else
				right = mid -1;
		}
		
		return left;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

16930번: 달리기

진영이는 다이어트를 위해 N×M 크기의 체육관을 달리려고 한다. 체육관은 1×1 크기의 칸으로 나누어져 있고, 칸은 빈 칸 또는 벽이다. x행 y열에 있는 칸은 (x, y)로 나타낸다. 매 초마다 진영이는

www.acmicpc.net

 

 


 

 

 

  • 풀이

 

시작점(x1,y1)부터 끝점(x2,y2)까지 최단거리로 이동할 수 있는 값 or 이동할 수 없으면 -1을 출력하면 되는 문제입니다.

 

조건은 아래와 같습니다.

  • 벽은 지나갈 수 없다.
  • 1초에 이동할 수 있는 칸의 최대 개수는 K개 이다.

 

위의 조건을 감안하여 해결해낸 풀이는 아래와 같습니다.

 

  1. 큐를 통해 좌표를 꺼내옵니다.
  2. 상, 하, 좌, 우를 각각 K번씩 이동합니다.
    1. 벽이 있으면 더이상 이동하지 않습니다.
    2. 한 번도 이동한적이 없다면 값을 초기화 해주고 큐에 추가합니다.
    3. 현재 이동할 값이랑 같다면 추가해줄 필요가 없으므로 지나갑니다.
  3. 이동한 값이 (x2,y2)라면 값을 초기화해 준 후 종료합니다.

 

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, M, K, x1, x2, y1, y2, answer;
	static boolean[][] wall;
	static int[][] visit;
	static int[] dx = { 0, 0, -1, 1 };
	static int[] dy = { -1, 1, 0, 0 };

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		N = in.nextInt();
		M = in.nextInt();
		K = in.nextInt();
		wall = new boolean[N + 1][M + 1];
		for (int i = 1; i <= N; i++) {
			String s = in.nextLine();
			for (int j = 1; j <= M; j++) {
				if(s.charAt(j - 1)=='#')
					wall[i][j] = true;
			}
		}

		x1 = in.nextInt();
		y1 = in.nextInt();
		x2 = in.nextInt();
		y2 = in.nextInt();
		answer = Integer.MAX_VALUE;
		visit = new int[N + 1][M + 1];
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= M; j++) {
				visit[i][j] = Integer.MAX_VALUE;
			}
		}

		bfs();

		// 이동할 수 없는 경우
		if (answer == Integer.MAX_VALUE)
			answer = -1;
	}

	private static void bfs() {
		Queue<Node> pq = new LinkedList<>();
		pq.add(new Node(x1, y1, 0));
		visit[x1][y1] = 0;

		while (!pq.isEmpty()) {
			Node node = pq.poll();
			
			if (node.x == x2 && node.y == y2) {
				answer = Math.min(answer, node.distance);
				break;
			}

			for (int direction = 0; direction < 4; direction++) {
				for (int k = 1; k <= K; k++) {
					int r = node.x + dx[direction]*k;
					int c = node.y + dy[direction]*k;

					if(r < 1 || c < 1 || r > N || c > M || wall[r][c]) break;

					if (visit[r][c] == Integer.MAX_VALUE) {
						visit[r][c] = node.distance + 1;
						pq.add(new Node(r,c,node.distance + 1));
					}
					else if (visit[r][c] == node.distance + 1) {
						continue;
					}
					else break;
				}
			}
		}
	}
}

class Node{
	int x;
	int y;
	int distance;

	public Node(int x, int y, int distance) {
		this.x = x;
		this.y = y;
		this.distance = distance;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

 

+ Recent posts