[문제]

숌 회사에서 이번에 새로운 전략 시뮬레이션 게임 세준 크래프트를 개발하기로 하였다. 핵심적인 부분은 개발이 끝난 상태고, 종족별 균형과 전체 게임 시간 등을 조절하는 부분만 남아 있었다.

게임 플레이에 들어가는 시간은 상황에 따라 다를 수 있기 때문에, 모든 건물을 짓는데 걸리는 최소의 시간을 이용하여 근사하기로 하였다. 물론, 어떤 건물을 짓기 위해서 다른 건물을 먼저 지어야 할 수도 있기 때문에 문제가 단순하지만은 않을 수도 있다. 예를 들면 스타크래프트에서 벙커를 짓기 위해서는 배럭을 먼저 지어야 하기 때문에, 배럭을 먼저 지은 뒤 벙커를 지어야 한다. 여러 개의 건물을 동시에 지을 수 있다.

편의상 자원은 무한히 많이 가지고 있고, 건물을 짓는 명령을 내리기까지는 시간이 걸리지 않는다고 가정하자.

 

[입력]

첫째 줄에 건물의 종류 수 N(1 ≤ N ≤ 500)이 주어진다. 다음 N개의 줄에는 각 건물을 짓는데 걸리는 시간과 그 건물을 짓기 위해 먼저 지어져야 하는 건물들의 번호가 주어진다. 건물의 번호는 1부터 N까지로 하고, 각 줄은 -1로 끝난다고 하자. 각 건물을 짓는데 걸리는 시간은 100,000보다 작거나 같은 자연수이다. 모든 건물을 짓는 것이 가능한 입력만 주어진다.

 

[출력]

N개의 각 건물이 완성되기까지 걸리는 최소 시간을 출력한다.

 

[예제]

예제 입력1 예제 출력1
5
10 -1
10 1 -1
4 1 -1
4 3 1 -1
3 3 -1
10
20
14
18
17

 


>문제 풀이

위상정렬 문제였는데, 이론을 배우긴 했는데 문제로 풀어보는건 처음이라 다른 블로그의 코드를 참고하여 공부하였습니다.

문제 조건

1. 어떤 건물을 짓기 위해 다른 건물을 먼저 지어야 할 수도 있다.

2. 여러 건물을 동시에 지을 수 있다.

순환이(cycle) 없고 상대적 순서를 지켜서 수행해야 한다. -> 위상정렬 (Topological Sort)

inDegree: 선행건물들의 개수가 0인 애들을 큐에 넣어서 실행시간을 넣어놓고 이 건물을 선행 조건으로 하는 건물들을 살펴봐야한다.

선행 건물들 부터 실행시간을(여기서 말한 실행시간은 선행조건을 포함한 건설시간) 저장해놓고 다음 건물을 지어야 함으로 dp적인 요소가 들어갔다고 할 수 있다.(Memoization)

나도 다른 분의 코드와 설명을 참고하여 공부했기 때문에 이상 자세한 설명은 생략한다.

(세부적인 내용은 아래 블로그를 참고하길 바랍니다.)

https://subbak2.tistory.com/53

 

[BOJ 백준] 게임 개발(1516) Java

링크 : https://www.acmicpc.net/problem/1516 1516번: 게임 개발 첫째 줄에 건물의 종류 수 N(1 ≤ N ≤ 500)이 주어진다. 다음 N개의 줄에는 각 건물을 짓는데 걸리는 시간과 그 건물을 짓기 위해 먼저 지어져야

subbak2.tistory.com

 

>전체 코드

 

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

public class Main {
	static int N;
	static Building[] buildingList;
	static int input;
	static Queue<Integer> queue;
	static StringBuilder sb= new StringBuilder();
	
	public static void main(String[] args) throws IOException{
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
		//1.입력
		N= Integer.parseInt(br.readLine());
		buildingList= new Building[N+1];
        for(int i=1; i<=N; i++{
			buildingList[i]= new Building();
        }
		
		//2. N개의 건물정보 입력
		for(int i=1; i<=N; i++) {
			st= new StringTokenizer(br.readLine());
			
			buildingList[i].time= Integer.parseInt(st.nextToken());
			input= Integer.parseInt(st.nextToken());
			while(input!=-1) {
				buildingList[i].inDegree++;
				buildingList[input].adjList.add(i);
				input= Integer.parseInt(st.nextToken());
			}
		}
		
		//2. 위상정렬 재료 만들기
		//- 선행건물이 없는 경우(inDegree=0)인 경우 큐에 넣기
		queue= new ArrayDeque<Integer>();
		for(int i=1; i<=N; i++) {
			if(buildingList[i].inDegree==0) {
				queue.add(i);
			}
		}
		
		//3. 위상정렬
		while(!queue.isEmpty()) {
			//1) 선행건물이 더이상 없는 녀석을 뽑아서 최종 건설시간에 자기 자신을 더해줌
			int quePoll= queue.poll();
			buildingList[quePoll].result+= buildingList[quePoll].time;
			
			//2) 자신을 지어야 지을 수 있는 건물들에게 선행건물의 건설이 끝났음을 알림
			for(int i: buildingList[quePoll].adjList) {
				//2-1) 선행건물의 개수 빼주기
				buildingList[i].inDegree--;
				//2-2) 타겟건물은 아직 건물을 지을 수 없는 상태
				//->현재 상태에서 타겟 건물의 res는 선행건물의 건설시간 중 최댓값: res를 최댓값으로 갱신시켜야
				buildingList[i].result= Math.max(buildingList[i].result, buildingList[quePoll].result);
				//2-3) 타겟건물의 선행건물 개수가 0이면 큐에 넣기
				if(buildingList[i].inDegree==0) queue.add(i);
			}
		}
		
		for(int i=1; i<=N; i++) {
			sb.append(buildingList[i].result+"\n");
		}
		System.out.println(sb.toString());
		
	}//main
	
	public static class Building{
		int time;		//각 건물 건설시간
		int inDegree;	//건물의 선행조건 개수
		int result;		//선행건물 포함 건설시간
		ArrayList<Integer> adjList; //건물이 지어져야 지을수 있는 건물들
		
		public Building() {
			this.time= 0;
			this.inDegree= 0;
			this.result= 0;
			this.adjList= new ArrayList<Integer>();
		}
	}
        
}

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

 

1516번: 게임 개발

첫째 줄에 건물의 종류 수 N(1 ≤ N ≤ 500)이 주어진다. 다음 N개의 줄에는 각 건물을 짓는데 걸리는 시간과 그 건물을 짓기 위해 먼저 지어져야 하는 건물들의 번호가 주어진다. 건물의 번호는 1부

www.acmicpc.net

 

[문제]

1번부터 N번까지 번호가 붙여져 있는 학생들에 대하여 두 학생끼리 키를 비교한 결과의 일부가 주어져 있다. 단, N명의 학생들의 키는 모두 다르다고 가정한다. 예를 들어, 6명의 학생들에 대하여 6번만 키를 비교하였고, 그 결과가 다음과 같다고 하자.

1번 학생의 키 < 5번 학생의 키

3번 학생의 키 < 4번 학생의 키

5번 학생의 키 < 4번 학생의 키

4번 학생의 키 < 2번 학생의 키

4번 학생의 키 < 6번 학생의 키

5번 학생의 키 < 2번 학생의 키

이 비교 결과로부터 모든 학생 중에서 키가 가장 작은 학생부터 자신이 몇 번째인지 알 수 있는 학생들도 있고 그렇지 못한 학생들도 있다는 사실을 아래처럼 그림을 그려 쉽게 확인할 수 있다. a번 학생의 키가 b번 학생의 키보다 작다면, a에서 b로 화살표를 그려서 표현하였다.

1번은 5번보다 키가 작고, 5번은 4번보다 작기 때문에, 1번은 4번보다 작게 된다. 그러면 1번, 3번, 5번은 모두 4번보다 작게 된다. 또한 4번은 2번과 6번보다 작기 때문에, 4번 학생은 자기보다 작은 학생이 3명이 있고, 자기보다 큰 학생이 2명이 있게 되어 자신의 키가 몇 번째인지 정확히 알 수 있다. 그러나 4번을 제외한 학생들은 자신의 키가 몇 번째인지 알 수 없다.

학생들의 키를 비교한 결과가 주어질 때, 자신의 키가 몇 번째인지 알 수 있는 학생들이 모두 몇 명인지 계산하여 출력하는 프로그램을 작성하시오.

 

[입력]

첫째 줄에 학생들의 수 N (2 ≤ N ≤ 500)과 두 학생 키를 비교한 횟수 M (0 ≤ M ≤ N(N-1)/2)이 주어진다. 다음 M개의 각 줄에는 두 학생의 키를 비교한 결과를 나타내는 두 양의 정수 a와 b가 주어진다. 이는 번호가 a인 학생이 번호가 b인 학생보다 키가 작은 것을 의미한다.

 

[출력]

자신이 키가 몇 번째인지 알 수 있는 학생이 모두 몇 명인지를 출력한다.

 

[예제]

예제 번호 예제 입력 예제 출력
1 6 6
1 5
3 4
5 4
4 2
4 6
5 2
1
2 6 7
1 3
1 5
3 4
5 4
4 2
4 6
5 2
2
3 6 3
1 2
2 3
4 5
0

 


>문제 풀이

1~N번 까지의 학생들의 키 순서를 상대적으로 비교한 정보가 주어진다.

이 정보를 통해 몇 번째로 키가 큰지 알 수 있는 학생이 몇 명인지 출력하라

어떤 학생에 대하여 자신을 제외한 1~N까지의 학생과 키를 비교할 수 있는지 확인해야한다.

그러나 한 명씩 매번 연산을 한다면 시간초과가 발생한다.

>> 모든 경로를 구해놓고서 1~N까지의 학생들이 가능한지 안한지만 체크해야한다.

플로이드-워셜 알고리즘

: 음의 간선 or 양의 간선에서 최단거리 파악할 수 있음

: 음의 싸이클 있으면 안되고 N<500일 때 다익스트라 보다 효율이 좋다고 알고있다.

::구현::

1) (N과 M을 입력받고,) int[][] distance 배열 선언 후 INF=999999999로 초기화

연결 여부 확인 할 때 비정상값인 경우 거르기 위해서 INF로 초기화 해놓는 것이다.

2) 키 정보값을 입력 받는다.

ex) i번 학생 < j번 학생 인 경우 distance[i][j]= 1;

3) for문을 돌면서 distance[i][j] 와 distance[i][k]+distance[k][j] 중 최소값을 선택한다.

( i는 출발지, j는 목적지, k는 경유지 // distance[i][j]: i에서 j로 가는 거리 )

어느 한쪽이 INF이면 다른 쪽을 선택할 것이고, 둘 다 INF이면 4)번에서 걸러진다.

4) 2중 for문 :: 1~N까지 확인하면서 distance[i][j]나 distance[j][i]가 INF가 아닌 경우를 cnt++카운팅 해서

cnt==N-1인 경우 answer++ 카운팅

//자기 자신 distance[i][j]는 무조건 INF 이다.

//그러니까 자신을 뺀 나머지랑 연결되어 있어야 하는데 이것을 cnt= N-1을 체크해줌으로써 확인이 가능하다.

 

>전체 코드

큰 차이는 없지만 두 가지 방법으로 풀어보았다.

어차피 연결여부만 확인하면 된다는 부분에서, int 보다는 boolean이 더 목적에 맞을 것 같아서 2)번으로도 고쳐본 것이다. 위의 설명을 이해했다면 2)번은 int->boolean이기 때문에 코드만 봐도 이해가 될 것 이다.

1) int[][] 배열 이용

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

public class Main {
	
	public static void main(String[] args) throws IOException{
		final int INF= 999999999;
		int N, M, answer=0;
		int a, b;
		int[][] distance;
		
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
		st= new StringTokenizer(br.readLine());
		N= Integer.parseInt(st.nextToken());
		M= Integer.parseInt(st.nextToken());
		
		distance= new int[N+1][N+1];
		for(int i=1; i<N+1; i++) {
			Arrays.fill(distance[i], INF);
		}
		
		for(int i=0; i<M; i++) {
			st= new StringTokenizer(br.readLine());
			
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			
			distance[a][b]= 1; //a, b가 서로 연결되어있다.
		}
		
		//플로이드-워셜: 경유지-출발지-도착지 : 3중 for문
		//출발 i, 도착:j, 경유지:k
		for(int k=1; k<=N; k++) {
			for(int i=1; i<=N; i++) {
				for(int j=1; j<=N; j++) {
					distance[i][j]= distance[i][j]<=distance[i][k]+distance[k][j]?
									distance[i][j]:distance[i][k]+distance[k][j];
				}
			}
		}
		
		for(int i=1; i<=N; i++) {
			int cnt=0;
			for(int j=1; j<=N; j++) {
				if(distance[i][j]!=INF || distance[j][i]!=INF) cnt++;
			}
			if(cnt==N-1) answer++;
		}
		
        System.out.println(answer);
	}//main
        
}

2) boolean 배열

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

public class Main {
	
	public static void main(String[] args) throws IOException{
		int N, M, answer=0;
		int a, b;
		boolean[][] distance;
		
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
		st= new StringTokenizer(br.readLine());
		N= Integer.parseInt(st.nextToken());
		M= Integer.parseInt(st.nextToken());
		
		distance= new boolean[N+1][N+1];
		
		for(int i=0; i<M; i++) {
			st= new StringTokenizer(br.readLine());
			
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			
			distance[a][b]= true; //a, b가 서로 연결되어있다.
		}
		
		//플로이드-워셜: 경유지-출발지-도착지 : 3중 for문
		//출발 i, 도착:j, 경유지:k
		for(int k=1; k<=N; k++) {
			for(int i=1; i<=N; i++) {
				for(int j=1; j<=N; j++) {
					if(distance[i][k]&&distance[k][j]) {
						distance[i][j]= true;
					}
				}
			}
		}
		
		boolean check;
		for(int i=1; i<=N; i++) {
			check= false;
			for(int j=1; j<=N; j++) {
				//i에서 시작해서 연결이 안된 학생이 있다.
				if(i!=j && !distance[i][j] && !distance[j][i]) {
					check= true;
					break;
				}
			}
			if(!check) answer++;
		}
		
        System.out.println(answer);
	}//main
        
}

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

 

2458번: 키 순서

1번부터 N번까지 번호가 붙여져 있는 학생들에 대하여 두 학생끼리 키를 비교한 결과의 일부가 주어져 있다. 단, N명의 학생들의 키는 모두 다르다고 가정한다. 예를 들어, 6명의 학생들에 대하여

www.acmicpc.net

 

[문제]

동호와 규완이는 212호에서 문자열에 대해 공부하고 있다. 김진영 조교는 동호와 규완이에게 특별 과제를 주었다. 특별 과제는 특별한 문자열로 이루어 진 사전을 만드는 것이다. 사전에 수록되어 있는 모든 문자열은 N개의 "a"와 M개의 "z"로 이루어져 있다. 그리고 다른 문자는 없다. 사전에는 알파벳 순서대로 수록되어 있다.

규완이는 사전을 완성했지만, 동호는 사전을 완성하지 못했다. 동호는 자신의 과제를 끝내기 위해서 규완이의 사전을 몰래 참조하기로 했다. 동호는 규완이가 자리를 비운 사이에 몰래 사전을 보려고 하기 때문에, 문자열 하나만 찾을 여유밖에 없다.

N과 M이 주어졌을 때, 규완이의 사전에서 K번째 문자열이 무엇인지 구하는 프로그램을 작성하시오.

[입력]

첫째 줄에 N, M, K가 순서대로 주어진다. N과 M은 100보다 작거나 같은 자연수이고, K는 1,000,000,000보다 작거나 같은 자연수이다.

[출력]

첫째 줄에 규완이의 사전에서 K번째 문자열을 출력한다. 만약 규완이의 사전에 수록되어 있는 문자열의 개수가 K보다 작으면 -1을 출력한다.

[예제]

 


>문제 풀이

N개의 "a"와 M개의 "z"으로 만들수 있는 문자열을 사전순으로 정렬했을 때 K번째 문자열을 구하라.

N과 M이 100 이하의 자연수이므로 N과 M으로 조합 가능한 문자는 (200!)/(100!*100!) 개 이다.

즉, 이걸 하나씩 다 조합해보고 k번째 문자열을 구하기에는 시간도 메모리도 너무 비효율적이다.

그럼 어떻게 풀어야 할까?

사전순으로 a가 먼저이고 z가 다음이다. 즉, 조합할 때 순서가 있다는 것.

만약 a 3개와 z 3개로 만들 수 있는 문자열에 대해서 살펴본다고 했을 때

맨 앞글자가 a로 시작 + "a 2개와 z 3개로 만들 수 있는 문자열"

맨 앞글자가 z로 시작 + "a 3개와 z 2개로 만들 수 있는 문자열"

이렇게 될 것이다.

즉, dp[N][M]를 "a" N개와 "z" M개로 만들 수 있는 문자열의 개수 라고 할 때

dp[N][M]= dp[N-1][M] + dp[N][M-1] 의 점화식을 만들 수 있다.

** 여기까지는 금방 생각해 냈는데, 구현에서 막혔다.**

그래서 다른 분의 코드를 참고하였다.

참고한 블로그: https://velog.io/@sungjin0757/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-1256%EB%B2%88-%EC%82%AC%EC%A0%84JAVA

 

[알고리즘] - 백준 1256번 : 사전(JAVA)

문제 풀러가기dp 문제를 풀이하는 방식으로 접근을 하여야 하지만, 좀 더 알아야 할 것이 있었습니다!n개의 문자와 m개의 문자를 조합하여 만들 수 있는 문자열의 개수를 구할 수 있는 것이 시급

velog.io


먼저 함수를 두 가지로 나눈다.

check 함수와 makeS함수

함수 1) check(int a, int z) //a는 "a"의 개수, z: "z"의 개수

"a" a개와 "z" z개로 만들 수 있는 문자열 개수를 구하는 함수이다.

함수 2) makeS(int a, int z, int k) //k는 구하고자하는 문자열의 순서

"a" a개와 "z" z개로 만들수 있는 문자열들 중 k번째 문자열을 구하는 함수

<main>

//"a" N개와 "z" M개로 만들 수 있는 문자열의 개수가 K보다 작으면 -1출력

if( check(N, M) < K ) 일 때 -1출력

else{

   makeS(N, M, K);

   문자열 출력;

}


check 함수는 점화식을 그대로 구현한 것이라 아래 코드를 보면 어렵지 않은 것을 알 수 있다.

makeS 함수도 어렵지는 않다.

(* 문자는 하나씩("a"||"z") 붙여줘야 하기 때문에 StringBuilder를 사용했다.)

분기점 1)

a==0일 때는 남은 a가 없으니까 뒤로는 z로만 채우면 되고

z==0일 때는 남은 z가 없으니까 뒤로는 a로만 채우면 된다.

분기점 2)

// a로 시작하게 됐을 때 만들 수 있는 문자열의 개수를 구해본다.

double check= check(a-1, z);

if ( check < k ) {

     // 이면, a로 시작하게 되면 k번째 문자열을 포함하는 문자열을 못만든다는 것이다.

     // 그러면 "z"를 붙여줘야하고

     // makeS(a, z-1, k-check);

     // 왜?? a는 안썼고, z는 하나 썼으며

     // k에서 check를 빼주는 이유: z로 시작함으로써 a로 시작하는 문자열들의 개수는 빼고 넘겨주는 것

}else{

     // check>=k 이면, a로 시작했을 때 k번째 문자열을 구할 수 있다.

     // "a"를 붙여주고

     // makeS(a-1, z, k);

     // 왜?? a 하나 썼고, z는 안썼다.

     //그리고 a로 시작 했을 때 k번째 문자열을 구할 수 있으니까 그대로 넘겨준다.

}

 

>전체 코드

 

import java.util.Scanner;

public class Main {
	static double[][] dp= new double[101][101];
	static StringBuilder sb= new StringBuilder(); 
	
	public static void main(String[] args) {
		Scanner scan= new Scanner(System.in);
		
		//N은 a개수, M은 z개수, K번째 문자열을 구해야함.
		//N, M은 100이하, K는 10억 이하의 수
		int N= scan.nextInt();
		int M= scan.nextInt();
		double K= scan.nextDouble();
		
		if(check(N, M)<K) {
			System.out.println("-1");
		}else {
			makeS(N, M, K);
			System.out.println(sb.toString());
		}
	}//main
	
	//개수 구하는 함수
	public static double check(int a, int z) {
		if(a==0||z==0) return 1;
		if(dp[a][z]!=0) return dp[a][z];
		
		return dp[a][z]= Double.min(check(a-1, z)+check(a, z-1), 1000000001);
	}
	
	//문자열 만드는 함수
	public static void makeS(int a, int z, double k) {
		if(a==0) {
			for(int i=0; i<z; i++)
				sb.append("z");
			return;
		}
		if(z==0) {
			for(int i=0; i<a; i++)
				sb.append("a");
			return;
		}
		
		double check= check(a-1, z);
		if(k>check) {
			sb.append("z");
			makeS(a, z-1, k-check);
		}else {
			sb.append("a");
			makeS(a-1, z, k);
		}
	}//makeS	
}

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

 

1256번: 사전

첫째 줄에 N, M, K가 순서대로 주어진다. N과 M은 100보다 작거나 같은 자연수이고, K는 1,000,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

 

[문제]

한 배열 A[1], A[2], …, A[n]에 대해서, 부 배열은 A[i], A[i+1], …, A[j-1], A[j] (단, 1 ≤ i ≤ j ≤ n)을 말한다. 이러한 부 배열의 합은 A[i]+…+A[j]를 의미한다. 각 원소가 정수인 두 배열 A[1], …, A[n]과 B[1], …, B[m]이 주어졌을 때, A의 부 배열의 합에 B의 부 배열의 합을 더해서 T가 되는 모든 부 배열 쌍의 개수를 구하는 프로그램을 작성하시오.

예를 들어 A = {1, 3, 1, 2}, B = {1, 3, 2}, T=5인 경우, 부 배열 쌍의 개수는 다음의 7가지 경우가 있다.

T(=5) = A[1] + B[1] + B[2]
= A[1] + A[2] + B[1]
= A[2] + B[3]
= A[2] + A[3] + B[1]
= A[3] + B[1] + B[2]
= A[3] + A[4] + B[3]
= A[4] + B[2]

[입력]

첫째 줄에 T(-1,000,000,000 ≤ T ≤ 1,000,000,000)가 주어진다. 다음 줄에는 n(1 ≤ n ≤ 1,000)이 주어지고, 그 다음 줄에 n개의 정수로 A[1], …, A[n]이 주어진다. 다음 줄에는 m(1 ≤ m ≤ 1,000)이 주어지고, 그 다음 줄에 m개의 정수로 B[1], …, B[m]이 주어진다. 각각의 배열 원소는 절댓값이 1,000,000을 넘지 않는 정수이다.

[출력]

첫째 줄에 답을 출력한다. 가능한 경우가 한 가지도 없을 경우에는 0을 출력한다.

[예제]

 


>문제 풀이

 

A[ ]: 1 3 1 2

listA: 1, 4, 5, 7, 3, 4, 6, 1, 3, 2

sort: 1, 1, 2, 3, 3, 4, 4, 5, 6, 7

B[ ]: 1 3 2

listB: 1, 4, 6, 3, 5, 2

sort: 1, 2, 3, 4, 5, 6

List에 각 배열로 만들 수 있는 부분합을 저장해놓고, lower bound와 upper bound를 구하여

listA.get(i) + listB.get(?)==T인 개수를 구한다.

단, lower bound와 upper bound를 구하기 전에 sort를 해줘야 한다.

lower bound는 listA.get(i)+listB.get(lb)가 T이상인 값을 구하고

upper bound는 listA.get(i)+listB.get(ub)가 T초과인 값을 구한다.

그리고 cnt+= ub-lb;


+) 투 포인터 탐색 방법을 사용할 수도 있다.

+) map을 활용할 수도 있는데 그러면 코드가 더 간결해진다.

 

 

 

>전체 코드

 

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

public class Main {
	
	public static void main(String[] args) throws IOException {
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
		int T, N, M; //-십억<=T<=십억 // N: 1000이하의 정수, M: 1000 이하의 정수
		long[] A, B; //각 원소는 절댓값 백만 이하
		List<Long> listA= new ArrayList<>();
		List<Long> listB= new ArrayList<>();
		
		//입력
		T= Integer.parseInt(br.readLine());
		N= Integer.parseInt(br.readLine());
		A= new long[N];
		st= new StringTokenizer(br.readLine());
		for(int i=0; i<N; i++) {
			A[i]= Long.parseLong(st.nextToken());
		}
		
		M= Integer.parseInt(br.readLine());
		B= new long[M];
		st= new StringTokenizer(br.readLine());
		for(int i=0; i<M; i++) {
			B[i]= Long.parseLong(st.nextToken());
		}
		
		//나올 수 있는 합 구해서 list에 넣기
		long sum=0;
		for(int i=0; i<N; i++) {
			sum= A[i];
			listA.add(sum);
			for(int j=i+1; j<N; j++) {
				sum+=A[j];
				listA.add(sum);
			}
		}
		listA.sort(null);
		
		for(int i=0; i<M; i++) {
			sum= B[i];
			listB.add(sum);
			for(int j=i+1; j<M; j++) {
				sum+=B[j];
				listB.add(sum);
			}
		}
		listB.sort(null);
		
		//계산 및 출력
		long cnt=0;
		int left, right, mid, lb, ub;
		for(int i=0; i<listA.size(); i++) {
			//lower
			left=0;
			right=listB.size();
			while(left<right) {
				mid=(left+right)/2;
				if(listA.get(i)+listB.get(mid)>=T){
					right= mid;
				}else {
					left= mid+1;
				}
			}
			lb= left;
			
			//upper
			left=0;
			right=listB.size();
			while(left<right) {
				mid=(left+right)/2;
				if(listA.get(i)+listB.get(mid)<=T){
					left= mid+1;
				}else {
					right= mid;
				}
			}
			ub= left;
			cnt+=ub-lb;
		}//for
		
		System.out.println(cnt);
	}//main
}

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

 

2143번: 두 배열의 합

첫째 줄에 T(-1,000,000,000 ≤ T ≤ 1,000,000,000)가 주어진다. 다음 줄에는 n(1 ≤ n ≤ 1,000)이 주어지고, 그 다음 줄에 n개의 정수로 A[1], …, A[n]이 주어진다. 다음 줄에는 m(1 ≤ m ≤ 1,000)이 주어지고, 그

www.acmicpc.net

 

[문제]

수정이는 어린 동생을 달래기 위해서 사탕을 사용한다. 수정이는 평소에 여러 개의 사탕을 사서 사탕상자에 넣어두고, 동생이 말을 잘 들을 때면 그 안에서 사탕을 꺼내서 주곤 한다.

각각의 사탕은 그 맛의 좋고 나쁨이 1부터 1,000,000까지의 정수로 구분된다. 1이 가장 맛있는 사탕을 의미하며, 1,000,000은 가장 맛없는 사탕을 의미한다. 수정이는 동생이 말을 잘 들은 정도에 따라서, 사탕상자 안에 있는 사탕들 중 몇 번째로 맛있는 사탕을 꺼내주곤 한다. 예를 들어 말을 매우 잘 들었을 때에는 사탕상자에서 가장 맛있는 사탕을 꺼내주고, 말을 조금 잘 들었을 때에는 사탕상자에서 여섯 번째로 맛있는 사탕을 꺼내주는 식이다.

수정이가 보관하고 있는 사탕은 매우 많기 때문에 매번 사탕상자를 뒤져서 꺼낼 사탕을 골라내는 일은 매우 어렵다. 수정이를 도와주는 프로그램을 작성하시오.

[입력]

첫째 줄에 수정이가 사탕상자에 손을 댄 횟수 n(1≤n≤100,000)이 주어진다. 다음 n개의 줄에는 두 정수 A, B, 혹은 세 정수 A, B, C가 주어진다. A가 1인 경우는 사탕상자에서 사탕을 꺼내는 경우이다. 이때에는 한 정수만 주어지며, B는 꺼낼 사탕의 순위를 의미한다. 이 경우 사탕상자에서 한 개의 사탕이 꺼내지게 된다. 또, A가 2인 경우는 사탕을 넣는 경우이다. 이때에는 두 정수가 주어지는데, B는 넣을 사탕의 맛을 나타내는 정수이고 C는 그러한 사탕의 개수이다. C가 양수일 경우에는 사탕을 넣는 경우이고, 음수일 경우에는 빼는 경우이다. 맨 처음에는 빈 사탕상자에서 시작한다고 가정하며, 사탕의 총 개수는 2,000,000,000을 넘지 않는다. 또한 없는 사탕을 꺼내는 경우와 같은 잘못된 입력은 주어지지 않는다.

[출력]

A가 1인 모든 입력에 대해서, 꺼낼 사탕의 맛의 번호를 출력한다. 물론, A=2 이면서 C<0 일 때는 출력하지 않는다.

[예제]

 

 


>문제 이해

설명)) 사탕의 맛은 좋고 나쁨이 1~ 1,000,000의 정수로 구분된다.

= 나쁨수치 (숫자가 작을수록 맛있고, 높을수록 맛없음)

input))

N: 수정이가 사탕상자에 손을 댄 횟수

N개의 줄에는) A, B 또는 A, B, C가 주어진다.

1) A==1이면? 상자에서 사탕 꺼내는

B=꺼낼 사탕의 순위

2) A==2이면? 사탕을 넣는 경우

B=넣을 사탕의 맛

C=사탕 개수 (음수면 뺄셈)

사탕의 총 개수는 2,000,000,000을 넘지 않는다.

잘못된 입력은 주어지지 않는다.

output))

A가 1인 모든 입력에 대해: 꺼낼 사탕의 맛의번호 출력

A가 2이면서 C<0이면 출력X

>문제 풀이

1. 인덱스의 따른 값이 계속해서 바뀐다.

2. 순위 정보가 필요한데 값이 계속 바뀐다. 범위값이 백만이라 누적합을 매번 구한다면 시간초과가 남.

=> 인덱스 트리를 사용한다.

그래서 포화 이진 트리를 만들어야 하는데, 백만 이상의 2의 제곱수 만큼의 트리를 만들어야한다.

왜냐면, 위처럼 포화 이진 트리를 만들 때, 만약 리프노드가 8개이면 총 15개의 공간이 필요한데, 노드의 번호를 편하게 쓰려면 0을 제외하고 15개의 공간을 만들어야함. 즉 2*leaf=16 공간이 필요하게된다.

이 문제에서는 사탕의 맛의 정도가 1~1,000,000 라서 리프 노드만 백만개이다.

트리를 만들 때는 완전 이진 트리를 만들어야 계산할 수 있으므로 백만이 넘는 2의 제곱수가 필요하다.

* 트리 구조에서 부모노드와 자식노드의 번호의 관계

parent= left/2 (또는 right/2);

left= parent*2;

right= parent*2+1;

 

 

>전체 코드

 

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


public class Main {
	static int tree[];
	static int N, S, A, B, C;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		
        //포화 이진트리 생성을 위한 2^n>1000000인 S(=2^n) 찾기
		S=1;
		while(S<1000000) {
			S*=2;
		}
		tree= new int[S*2];
		
		N= Integer.parseInt(br.readLine());
		StringBuilder sb= new StringBuilder();
		for(int i=0; i<N; i++) {
			st= new StringTokenizer(br.readLine());
			A= Integer.parseInt(st.nextToken());
			if(A==1) {
				B= Integer.parseInt(st.nextToken());
				//사탕 꺼내기, B번째
				int index= pickup(1, S, 1, B);
				sb.append(index+"\n");
				update(1, S, 1, index, -1);
			}else {
				B= Integer.parseInt(st.nextToken());
				C= Integer.parseInt(st.nextToken());
				//사탕 넣기 B맛 C개(양수+, 음수-)
				update(1, S, 1, B, C);
			}
		}
		System.out.println(sb.toString());
	}//main
	
    //left, right는 범위(1~S), node: 현재 위치한 노드의 인덱스, target: 목표 노드
	public static int pickup(int left, int right, int node, int target) {
		if(left==right) return left;
		
		int mid= (left+right)/2;
		if(tree[node*2]>=target) { //왼쪽 노드이면 (target그대로)
			return pickup(left, mid, node*2, target);
		}else { //오른쪽 노드이면 (target-왼쪽 노드)
			return pickup(mid+1, right, node*2+1, target-tree[node*2]);
		}
	}
	
    //(left, right, node, target, diff: 더하거나 뺄 사탕개수)
	public static void update(int left, int right, int node, int target, int 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);
		}
	}	
}

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

 

2243번: 사탕상자

첫째 줄에 수정이가 사탕상자에 손을 댄 횟수 n(1≤n≤100,000)이 주어진다. 다음 n개의 줄에는 두 정수 A, B, 혹은 세 정수 A, B, C가 주어진다. A가 1인 경우는 사탕상자에서 사탕을 꺼내는 경우이다.

www.acmicpc.net

 

+ Recent posts