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

 

11779번: 최소비용 구하기 2

첫째 줄에 도시의 개수 n(1≤n≤1,000)이 주어지고 둘째 줄에는 버스의 개수 m(1≤m≤100,000)이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스

www.acmicpc.net


풀이 과정

어떤 도시에서 출발해, 도착지점까지 버스를 타고 가는 최소 비용을 구하고, 그 경로를 출력하는 문제이다.

 

음수 가중치가 존재하지 않는, 가중치가 존재하는 그래프에서의 최소 비용을 구하는 문제이므로 다익스트라 알고리즘을 활용해서 문제를 해결할 수 있다. 우선순위 큐를 활용한 다익스트라 알고리즘의 시간복잡도는 O(ElogV)이므로 TLE도 발생하지 않음을 확인할 수 있다.

 

다익스트라 알고리즘 진행 도중, 다음 정점까지 가는 비용이 현재 정점을 경유하는 경우가 더 저렴하면, pre[nxt] = cur로 nxt 정점을 최소 비용으로 방문하기 위해서는 이전 정점으로 cur 정점을 거쳐야 한다는 점을 표시해주자. 그렇다면 end까지 도착했을때, end부터 계속 pre 배열을 확인하면 start부터 end까지 최소비용으로 방문했을 때의 경로를 역순으로 얻어낼 수 있으며, 이를 다시 뒤집어서 출력하면 경로를 반환할 수 있다.


소스 코드

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

public class Main {
	static StringBuilder sb = new StringBuilder();

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine()); // 도시의 개수를 입력받는다
		int m = Integer.parseInt(br.readLine()); // 버스의 개수를 입력받는다
		ArrayList<ArrayList<int[]>> al = new ArrayList<>(); // u번째 버스에서 출발하는 (비용, 도착지) 버스 노선을 저장한다
		int[] d = new int[n+1]; // 시작지로부터의 비용을 저장한다
		int[] pre = new int[n+1]; // 시작지점부터 특정 도시까지 최소비용으로 도달하기 위한 경로의 바로 이전 도시를 저장한다
		
		for (int i = 0; i <= n; i++) {
			al.add(new ArrayList<>()); // 인접 리스트 초기화
			d[i] = Integer.MAX_VALUE; // 시작지로부터의 비용을 MAX로 초기화한다
		}
		
		for (int i = 0; i < m; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int u = Integer.parseInt(st.nextToken()); // 출발지 입력
			int v = Integer.parseInt(st.nextToken()); // 도착지 입력
			int w = Integer.parseInt(st.nextToken()); // 비용 입력
			al.get(u).add(new int[] {w, v}); // u번째 버스에서 출발하는 버스들의 리스트에 (비용, 도착지) 삽입
		}
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		int start = Integer.parseInt(st.nextToken()); // 시작지점 입력
		int end = Integer.parseInt(st.nextToken()); // 도착지점 입력
		
		// (비용, 도착지)에서 비용의 오름차순으로 우선순위를 갖는 우선순위 큐 선언
		PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
		d[start] = 0; // 시작지점까지의 거리는 0
		pq.add(new int[] {d[start], start}); // pq에 가장 거리가 가까운 점을 넣는다
		
		// 다익스트라 진행
		while(!pq.isEmpty()) {
			int[] cur = pq.poll(); // 우선순위 큐에서 하나 빼고
			
			// 뺀 정점까지의 거리가 갱신된 시작정점까지의 거리와 다르면 쓰레기 값이므로 다른 값을 뺀다
			if (cur[0] != d[cur[1]]) continue; 
			
			// 현재 보고 있는 정점에서 경로가 존재하는 모든 도착도시에 대해
			for (int[] nxt : al.get(cur[1])) {
				
				// 도착도시까지의 거리가 현재 보고있는 정점을 거쳐가는게 더 비용이 적은 경우에 갱신한다
				if (d[nxt[1]] <= d[cur[1]] + nxt[0]) continue;
				d[nxt[1]] = d[cur[1]] + nxt[0];
				
				// 그리고 우선순위 큐에 넣는다
				pq.add(new int[] {d[nxt[1]], nxt[1]});
				// 현재 정점을 거쳐가는 것이 nxt까지의 경로중 가장 비용이 적으므로, 저장한다
				pre[nxt[1]] = cur[1];
			}
		}
		
		// 최소 비용을 출력한다
		sb.append(d[end]).append('\n');
		
		// 도착정점부터 pre 배열을 살펴보며 최소 비용으로 순회하는 경로를 역순으로 스택에 삽입한다
		ArrayDeque<Integer> q = new ArrayDeque<>();
		int index = end;
		q.addLast(index);
		
		while (index != start) {
			index = pre[index];
			q.addLast(index);
		}
		
		sb.append(q.size()).append('\n');
		
		// 스택에서 다시 빼면서 출력하면 출발도시에서 도착도시까지의 경로가 출력된다
		while(!q.isEmpty()) {
			sb.append(q.pollLast()).append(' ');
		}
		
		System.out.print(sb.toString());
	}
}

 

 

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

 

1197번: 최소 스패닝 트리

첫째 줄에 정점의 개수 V(1 ≤ V ≤ 10,000)와 간선의 개수 E(1 ≤ E ≤ 100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이

www.acmicpc.net


풀이 과정

그래프에서 최소 스패닝 트리를 구하는 문제이다.

 

최소 스패닝 트리를 구하는 방법은 크루스칼 알고리즘, 프림 알고리즘이 존재하고 일반적으로 구현이 간단한 크루스칼 알고리즘을 많이 사용한다.

 

크루스칼 알고리즘에서는, 가중치가 가장 낮은 간선을 우선으로 노드를 연결하는데, 이미 연결되어 있는 노드상의 간선이면 연결하지 않는다. 이 때, 이미 연결되어 있는지 여부의 판단을 서로소 집합으로 구하게 된다.


소스 코드

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

public class Main {
	static StringBuilder sb = new StringBuilder();
	static int[] parents;
	
	static void union(int a, int b) { // 서로소 집합 합치기
		int aRoot = find(a);
		int bRoot = find(b);
		if (aRoot == bRoot) return;
		parents[bRoot] = aRoot;
	}
	
	static int find(int a) { // 서로소 집합의 대표자 찾기
		if (a == parents[a]) return a;
		else return parents[a] = find(parents[a]); // 경로 압축
	}

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int V = Integer.parseInt(st.nextToken());
		int E = Integer.parseInt(st.nextToken());
		parents = new int[V+1];
		
		// 가중치가 가장 작은 것을 가지고 있는 간선을 루트로 하는 최소 힙 선언
		PriorityQueue<int[]> edges = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
		int answer = 0;
		int connect = 0;
		
		for (int i = 0; i <= V; i++) {
			parents[i] = i; // 대표자 초기화
		}
		
		for (int i = 0; i < E; i++) {
			st = new StringTokenizer(br.readLine());
			int u = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			int w = Integer.parseInt(st.nextToken());
			
			edges.add(new int[] {w, u, v}); // (가중치, 시작노드, 도착노드 순으로 입력)
		}
		
		while (connect < V-1) { // 간선-1개의 간선이 연결되면 최소 스패닝 트리 완성
			int[] cur = edges.poll();
			
			// 같은 서로소 집합에 속한지 파악해서 이미 트리에 연결되어 있는 노드끼리의 간선이면 무시한다
			if (find(cur[1]) == find(cur[2])) {
				continue;
			}
			
			// 정답 가중치 증가, 이어진 간선 개수 증가, 같은 서로소 지합으로 만든다
			answer += cur[0];
			connect++;
			union(cur[1], cur[2]);
		}
		
		System.out.println(answer);
	}
}

 

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

 

7662번: 이중 우선순위 큐

입력 데이터는 표준입력을 사용한다. 입력은 T개의 테스트 데이터로 구성된다. 입력의 첫 번째 줄에는 입력 데이터의 수를 나타내는 정수 T가 주어진다. 각 테스트 데이터의 첫째 줄에는 Q에 적

www.acmicpc.net


풀이 과정

최소값과 최대값을 뽑아낼 수 있는 이중 우선순위 큐를 구현하는 문제이다.

 

최소 힙, 최대 힙 2개를 선언해서 풀이하는 방법도 있지만 자바에서는 최소값 반환과 최대값 반환이 O(logN)으로 가능한 이진 검색 트리, 그 중에서도 레드 블랙 트리로 구현된 트리셋 자료구조가 존재해 트리셋 자료구조를 활용해 문제를 해결하였다.

 

트리셋은 Set이므로, 중복된 수를 여러개 담을 수 없기에, 특정 수의 개수를 저장할 해쉬맵을 선언하여 문제를 해결하였다. 소스의 주석을 보면 이해할 수 있다.


소스 코드

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

public class Main {
	static StringBuilder sb = new StringBuilder();

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		
		for (int tc = 1; tc <= T; tc++) {
			int k = Integer.parseInt(br.readLine());
			HashMap<Integer, Integer> hm = new HashMap<>(); // 숫자의 개수를 저장할 해쉬맵
			TreeSet<Integer> ts = new TreeSet<>(); // 이중 우선순위 큐 역할을 할 트리셋
			for (int i = 0; i < k; i++) {
				StringTokenizer st = new StringTokenizer(br.readLine());
				String command = st.nextToken();
				int input = Integer.parseInt(st.nextToken());
				
				if (command.equals("I")) { // 입력이면
					// 이미 입력받았던 거면 개수를 증가시킨다
					if (hm.containsKey(input)) hm.put(input, hm.get(input)+1);
					
					// 없었던거면 해쉬맵에 개수 1개로 표시하고 트리셋에 넣는다
					else {
						hm.put(input, 1);
						ts.add(input);
					}
				} else {
					if (ts.isEmpty()) continue; // 비었는데 삭제 연산은 무시
					
					if (input == 1) { // 최대값 출력
						int maxValue = ts.last();
						
						// 같은 수를 2개 이상 담겨있으면 해쉬맵을 보고 개수만 하나 줄여준다
						if (hm.get(maxValue) > 1) hm.put(maxValue, hm.get(maxValue) - 1);
						
						// 아니면 해쉬맵에서 제거하고 트리셋에서도 제거한다
						else {
							ts.pollLast();
							hm.remove(maxValue);
						}
					} else { // 최소값 출력에서도 같은 연산을 수행한다
						int minValue = ts.first();
						if (hm.get(minValue) > 1) hm.put(minValue, hm.get(minValue) - 1);
						else {
							ts.pollFirst();
							hm.remove(minValue);
						}
					}
				}
			}
			
			if (ts.isEmpty()) {
				sb.append("EMPTY");
			} else {
				sb.append(ts.last()).append(' ').append(ts.first());
			}
			
			sb.append('\n');
		}
		
		System.out.print(sb.toString());
	}
}

 

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

 

11779번: 최소비용 구하기 2

첫째 줄에 도시의 개수 n(1≤n≤1,000)이 주어지고 둘째 줄에는 버스의 개수 m(1≤m≤100,000)이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스

www.acmicpc.net


풀이 과정

도시와 도시 사이를 운행하는 버스가 있을 때, A번째 도시에서 B번째 도시로 가는 최소비용과 경로를 출력하는 문제이다.

  • 음수 간선 없는 최단거리 -> 다익스트라 알고리즘
  • 특정 정점 i를 최단거리로 방문하기 위해서 바로 직전 정점으로 j를 방문해야 한다면 pre[i] = j로 표시
  • 마지막 정점에서 역추적하면서 최단경로를 역순으로 뽑아낼 수 있으며, 이를 다시 역순으로 출력해주면 된다.

소스 코드

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

public class Main {
	static StringBuilder sb = new StringBuilder();

	public static void main(String[] args) throws IOException { // 메모리 56776KB 시간 632ms
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		int m = Integer.parseInt(br.readLine());
		ArrayList<ArrayList<int[]>> al = new ArrayList<>();
		int[] d = new int[n+1];
		int[] pre = new int[n+1];
		
		for (int i = 0; i <= n; i++) {
			al.add(new ArrayList<>());
			d[i] = Integer.MAX_VALUE;
		}
		
		for (int i = 0; i < m; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int u = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			int w = Integer.parseInt(st.nextToken());
			al.get(u).add(new int[] {w, v});
		}
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		int start = Integer.parseInt(st.nextToken());
		int end = Integer.parseInt(st.nextToken());
		PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
		d[start] = 0;
		pq.add(new int[] {d[start], start});
		
		while(!pq.isEmpty()) {
			int[] cur = pq.poll();
			
			if (cur[0] != d[cur[1]]) continue;
			
			for (int[] nxt : al.get(cur[1])) {
				if (d[nxt[1]] <= d[cur[1]] + nxt[0]) continue;
				d[nxt[1]] = d[cur[1]] + nxt[0];
				pq.add(new int[] {d[nxt[1]], nxt[1]});
				
				// nxt를 갈 때 cur를 거쳐가는게 더 빠르다면, pre[nxt] = cur로 설정
				pre[nxt[1]] = cur[1]; 
			}
		}
		
		sb.append(d[end]).append('\n');
		
		
		// 스택을 이용하여 끝에서부터 역추적
		ArrayDeque<Integer> q = new ArrayDeque<>();
		int index = end;
		q.addLast(index);
		
		while (index != start) {
			index = pre[index];
			q.addLast(index);
		}
		
		sb.append(q.size()).append('\n');
		
		// 스택에 넣은 뒤 다시 다 빼면 뒤집어서 나온다
		while(!q.isEmpty()) {
			sb.append(q.pollLast()).append(' ');
		}
		
		System.out.print(sb.toString());
	}
}

 

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

 

1406번: 에디터

첫째 줄에는 초기에 편집기에 입력되어 있는 문자열이 주어진다. 이 문자열은 길이가 N이고, 영어 소문자로만 이루어져 있으며, 길이는 100,000을 넘지 않는다. 둘째 줄에는 입력할 명령어의 개수

www.acmicpc.net


풀이 과정

문자열이 주어지고, 문자열 안에서 커서를 왼쪽, 오른쪽으로 이동시키며 커서 왼쪽의 문자를 추가하거나 삭제시키는 연산을 구현하는 문제이다.

 

문자열을 배열로 생각한다면 문자열에서 문자를 추가하거나 삭제하는 연산은 O(N)의 시간복잡도를 가진다. 추가된 문자 뒤쪽에 있는 문자를 한 칸씩 밀거나, 삭제된 문자 뒤쪽에 있는 문자들을 한 칸씩 당겨야 하기 때문이다. 따라서 문자의 추가와 삭제가 O(1)에 이루어질 수 있는 링크드 리스트를 사용해 문제를 해결해야 한다.

 

이 문제는 추가, 삭제 연산이 입력으로 들어오는 index에서 이루어 지는 것이 아닌, 커서 왼쪽에서 이루어진다는 것이 고정되어 있으므로 2개의 스택을 활용하여, 왼쪽 스택에는 커서 왼쪽에 있는 문자들을 저장, 오른쪽 스택에는 커서 오른쪽에 있는 문자들을 저장하는 방법으로 문제를 해결할 수도 있다.


소스 코드

2개의 스택 - 자바

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

public class Main { // 메모리 : 142208KB / 시간 608ms
	static StringBuilder sb = new StringBuilder();
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		ArrayDeque<Character> lStack = new ArrayDeque<>(); // 커서 왼쪽에 있는 문자들을 lStack에 저장한다
		ArrayDeque<Character> rStack = new ArrayDeque<>(); // 커서 오른쪽에 있는 문자들을 rStack에 저장한다
		char[] str = br.readLine().toCharArray();
		
		for (int i = 0; i < str.length; i++) {
			lStack.addLast(str[i]); // 초기에는 커서가 문자열의 가장 오른쪽에 있으므로 모든 문자를 lStack에 넣는다
		}
		
		int N = Integer.parseInt(br.readLine());
		
		for (int i = 0; i < N; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			char temp = st.nextToken().charAt(0);
			
			switch (temp) {
			case 'L': // L이 들어오면 커서를 왼쪽으로 한 칸 옮긴다
//				커서 왼쪽에 있는 문자 개수가 줄고, 커서 오른쪽에 있는 문자 개수가 하나 느는것과 같다
//				lStack의 원소를 rStack으로 하나 옮겨준다
				if (!lStack.isEmpty()) rStack.addLast(lStack.pollLast());
				break;
			case 'D': // D가 들어오면 커서를 오른쪽으로 한 칸 옮긴다
//				커서 오른쪽에 있는 문자 개수가 줄고, 커서 왼쪽에 있는 문자 개수가 하나 느는것과 같다
//				rStack의 원소를 lStack으로 하나 옮겨준다
				if (!rStack.isEmpty()) lStack.addLast(rStack.pollLast());
				break;
			case 'B': // B가 들어오면 커서 왼쪽에 있는 문자가 하나 없어진다
				if (!lStack.isEmpty()) lStack.pollLast(); // lStack에서 원소를 하나 제거해준다
				break;
			case 'P': // P가 들어오면 다음 오는 문자를 커서 왼쪽에 붙인다
				char temp2 = st.nextToken().charAt(0);
				lStack.addLast(temp2); // 다음 오는 문자를 lStack에 하나 넣어준다
				break;
			}
		}
		
		
//		lStack의 모든 문자를 rStack으로 옮긴 후, rStack의 모든 문자를 pop() 하면서 출력해주면 문자열을 출력할 수 있다
		while (!lStack.isEmpty()) {
			rStack.addLast(lStack.pollLast());
		}
		
		while (!rStack.isEmpty()) {
			sb.append(rStack.pollLast());
		}
		
		System.out.print(sb.toString());
	} // end of main
} // end of class

 

2개의 스택 - 파이썬

import sys
input = sys.stdin.readline

# 메모리 38664KB / 시간 620ms

l_stack = list(map(str, input().rstrip()))
r_stack = []
N = int(input())

for i in range(N):
    command = input().split()
    if command[0] == "L":
        if len(l_stack) != 0:
            r_stack.append(l_stack.pop())
    elif command[0] == "D":
        if len(r_stack) != 0:
            l_stack.append(r_stack.pop())
    elif command[0] == "B":
        if len(l_stack) != 0:
            l_stack.pop()
    elif command[0] == "P":
        l_stack.append(command[1])

while len(l_stack) > 0:
    r_stack.append(l_stack.pop())

while len(r_stack) > 0:
    print(r_stack.pop(), end="")

 

링크드 리스트 - 자바

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

public class Main { // 메모리 118980KB / 시간 624ms
	static StringBuilder sb = new StringBuilder();
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		char[] str = br.readLine().toCharArray();
		int N = str.length;
		LinkedList<Character> ll = new LinkedList<>();
		
		for (int i = 0 ; i < N; i++) {
			ll.add(str[i]);
		}
		
		ListIterator<Character> iter = ll.listIterator(); // 리스트에서 커서 위치를 저장할 ListIterator을 만든다
		
		while (iter.hasNext()) { // 
			iter.next(); // iter에 대입해줄 필요 없이 알아서 iter를 다음으로 넘겨줌
		}
		
		int M = Integer.parseInt(br.readLine());
		
		for (int i = 0; i < M; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			char temp = st.nextToken().charAt(0);
			switch (temp) {
			case 'L':
				if (iter.hasPrevious()) iter.previous(); // 링크드리스트에 이전 노드가 있으면 그 노드를 가리키도록 함
				break;
			case 'D':
				if (iter.hasNext()) iter.next(); // 링크드리스트에 다음 노드가 있으면 그 노드를 가리키도록 함
				break;
			case 'B':
				if (iter.hasPrevious()) { // 링크드 리스트에 이전 노드가 있으면 그 노드를 가리킨 뒤 지움
					iter.previous();
					iter.remove();
				}
				break;
			case 'P':
				char temp2 = st.nextToken().charAt(0);
				iter.add(temp2); // 링크드의 현재 가리키고 있는 노드의 index에 temp2가 오도록 추가함
				break;
			}
		}
		
		for (char c : ll) {
			sb.append(c);
		}
		
		System.out.print(sb.toString());
		
	} // end of main
} // end of class

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

 

2304번: 창고 다각형

첫 줄에는 기둥의 개수를 나타내는 정수 N이 주어진다. N은 1 이상 1,000 이하이다. 그 다음 N 개의 줄에는 각 줄에 각 기둥의 왼쪽 면의 위치를 나타내는 정수 L과 높이를 나타내는 정수 H가 한 개의

www.acmicpc.net


풀이 과정

지붕의 수평 부분이 어떤 기둥의 윗면, 지붕의 수직 부분이 어떤 기둥의 옆면과 닿아야 하고, 지붕에 오목한 부분이 있으면 안될 때 창고 다각형의 최소 값을 구하는 문제이다.

 

지붕의 높이가 높았다가, 낮았다가, 다시 높아지는 경우에 지붕에 오목한 부분이 생기게 된다. 막대의 높이가 가장 높은 곳을 T라 했을 때, 지붕의 시작점부터 T까지는 지붕의 높이가 높아지기만 해야 하고, T부터 지붕의 끝까지는 지붕의 높이가 낮아지기만 해야 한다. 그렇지 않으면 오목한 부분이 생기게 될 것이다.

 

막대의 길이를 쭉 입력 받고, 막대의 높이가 가장 높은 곳 T를 기준으로 반씩 나눠서, 지붕의 시작부터 T까지는 지붕의 높이가 점점 높아지게, T부터 지붕의 끝 까지는 지붕의 높이가 점점 낮아지게끔 막대를 보고 지붕의 높이를 설정해주면 지붕에 오목한 부분이 생기지 않게끔 창고 다각형의 최소 값을 구할 수 있다.


소스 코드

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

public class Main { // 메모리 11980kb / 시간 84ms
    static StringBuilder sb = new StringBuilder();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        // 가장 큰 막대를 기준으로 절반 나눠서 진행할 것이므로 가장 높은 막대의 위치, 막대가 처음 나오는 위치, 막대가 마지막으로
        // 나오는 위치를 저장할 변수들을 선언하였다
        int maxHeight = Integer.MIN_VALUE;
        int maxSpace = Integer.MIN_VALUE;
        int finalSpace = Integer.MIN_VALUE;
        int startSpace = Integer.MAX_VALUE;

        int[] board = new int[1001]; // 4kb정도?  L이 1000이하까지 들어오므로 인덱스 편하게 쓰려고 1001까지 잡았다

        for (int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int L = Integer.parseInt(st.nextToken());
            int H = Integer.parseInt(st.nextToken());
            board[L] = H; // 보드에 저장

            if (maxHeight < H) { // 가장 높은 막대의 높이와 위치를 저장한다
                maxHeight = H;
                maxSpace = L;
            }

            startSpace = Math.min(L, startSpace); // 막대가 시작되는 위치를 구한다
            finalSpace = Math.max(L, finalSpace); // 막대가 끝나는 위치를 구한다
        }

        int maxTemp = Integer.MIN_VALUE;
        
        // 일단 가장 긴 막대 길이를 더한다
        int answer = maxHeight;

        // 시작지점부터 높은 막대 위치 직전까지 점검하면서 점검한 막대 길이중 가장 긴 길이를 더해 나간다
        for (int i = startSpace; i < maxSpace; i++) {
            maxTemp = Math.max(board[i], maxTemp);
            answer += maxTemp;
        }

        maxTemp = Integer.MIN_VALUE;
        
        // 끝 지점부터 높은 막대 위치 직후까지 점검하면서 점검한 막대 길이중 가장 긴 길이를 더해 나간다
        for (int i = finalSpace; i > maxSpace; i--) {
            maxTemp = Math.max(board[i], maxTemp);
            answer += maxTemp;
        }

        System.out.println(answer);
    } // end of main
}// end of class

 

import sys
input = sys.stdin.readline

# 메모리 31256kb / 시간 44ms

N = int(input())
board = [0 for _ in range(1001)]
maxSpace = 0
maxHeight = 0
startSpace = 1001
finalSpace = -1

for i in range(N):
    L, H = map(int, input().split())
    board[L] = H
    startSpace = min(startSpace, L)
    finalSpace = max(finalSpace, L)

    if maxHeight < H:
        maxHeight = H
        maxSpace = L

answer = maxHeight
max_temp = 0

for i in range(startSpace, maxSpace):
    max_temp = max(max_temp, board[i])
    answer += max_temp

max_temp = 0

for i in range(finalSpace, maxSpace, -1):
    max_temp = max(max_temp, board[i])
    answer += max_temp

print(answer)

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

 

10972번: 다음 순열

첫째 줄에 입력으로 주어진 순열의 다음에 오는 순열을 출력한다. 만약, 사전순으로 마지막에 오는 순열인 경우에는 -1을 출력한다.

www.acmicpc.net


풀이 과정

순열이 주어졌을 때, 사전순으로 다음에 오는 순열을 구하는 문제이다.

 

C++에서는 next_permutation 함수를 통해 다음 순열을 구할 수 있지만, 자바와 파이썬에서는 다음 순열을 구하는 내장 함수가 존재하지 않으므로 직접 다음 순열을 구하는 알고리즘을 구현해줘야 한다.

 

다음 순열을 구하는 알고리즘은 다음과 같은 4단계로 이루어진다.

1. a[i-1] < a[i]인 가장 큰 i를 찾는다.

2. a[i-1] < a[j]인 가장 큰 j를 찾는다.

3. a[i-1]과 a[j]를 교체한다

4. a[i]부터 끝까지 뒤집어준다

 

C++에서는 swap함수가 있고 파이썬은 한 줄로 스왑이 가능한데 함수를 따로 만들거나 보조 변수를 하나 두거나 해야 해서 코드가 좀 길어지는 감이 있는 것 같다.


소스 코드

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

public class Main {
	static StringBuilder sb = new StringBuilder();
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int N = Integer.parseInt(br.readLine());
		int[] arr = new int[N];
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		for (int i = 0; i < N; i++) {
			arr[i] = Integer.parseInt(st.nextToken());
		}
		
		int a = N-1, b = N-1;
		
		while (a > 0 && arr[a-1] > arr[a]) { // arr[i-1] < a[i]인 가장 큰 i를 탐색한다
			a--;
		}
		
		if (a == 0) {
			System.out.println(-1);
			return;
		}
		
		while (b > 0 && arr[a-1] > arr[b]) { // arr[i-1] < a[j]인 가장 큰 j를 탐색한다
			b--;
		}
		
// arr[i-1]과 a[j]의 값을 바꾼다
		int temp = arr[a-1];
		arr[a-1] = arr[b];
		arr[b] = temp;
		b = N-1;
		
// arr[i]부터 모든 순서를 뒤집는다
		while (a < b) {
			temp = arr[a];
			arr[a] = arr[b];
			arr[b] = temp;
			a++; b--;
		}
		
		
		
		for (int i = 0; i < N; i++) {
			sb.append(arr[i]).append(" ");
		}
		
		System.out.print(sb.toString());
	} // end of main
} // end of class

 

import sys
input = sys.stdin.readline

N = int(input())
a = list(map(int, input().split()))
i = len(a) - 1

while i > 0 and a[i-1] >= a[i]:  # a[i-1] < a[i]인 가장 큰 i를 찾는다
    i -= 1

if i <= 0:
    print(-1)
    exit(0)

j = len(a) - 1

while a[i-1] >= a[j]:  # a[i-1] < a[j]인 가장 큰 j를 찾는다
    j -= 1

a[i-1], a[j] = a[j], a[i-1]  # a[i-1]과 a[j]를 교체한다
j = len(a) - 1

while i < j:  # a[i]부터 끝까지 순열을 뒤집는다
    a[i], a[j] = a[j], a[i]
    i += 1
    j -= 1

print(' '.join(map(str, a)))  # a를 출력한다

 

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

 

1931번: 회의실 배정

(1,4), (5,7), (8,11), (12,14) 를 이용할 수 있다.

www.acmicpc.net


풀이 과정

회의의 시작 시간과 끝나는 시간이 주어질 때, 회의가 겹치지 않으면서 가장 많은 회의를 진행하는 경우를 구하는 문제이다.

 

가능한 많은 회의를 진행하려면 회의의 종료시간이 빠른 순서대로 회의를 진행하면 된다. 회의의 종료시간이 같은 두 개의 회의가 존재할 때는 어떻게 처리해야 할까?

(9, 10), (10, 10) 두 입력이 들어왔다 가정하자. 여기서 (10, 10)을 먼저 처리하면 (9, 10)이 그리디로 처리되지 않는다. 따라서 종료시간이 빠른 순서대로 처리하되, 종료시간이 같은 회의는 시작시간이 더 빠른 회의가 우선으로 배정되도록 처리해야 한다.


소스 코드

import sys

N = int(sys.stdin.readline())
meeting = []

for i in range(N):
    start, end = map(int, sys.stdin.readline().split())
    meeting.append([start, end])

meeting.sort(key=lambda x: (x[1], x[0]))

answer = 1
end = meeting[0][1]

for i in range(1, N):
    if meeting[i][0] >= end:
        answer += 1
        end = meeting[i][1]

print(answer)

 

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

 

1629번: 곱셈

첫째 줄에 A, B, C가 빈 칸을 사이에 두고 순서대로 주어진다. A, B, C는 모두 2,147,483,647 이하의 자연수이다.

www.acmicpc.net


풀이 과정

A를 B번 곱한 수를 C로 나눈 나머지를 구하는 문제이다.

 

A, B, C 모두 약 21억까지 수가 들어올 수 있는데 시간제한이 0.5초다. 반복문으로 A를 21억번 곱하는 방법으로 풀면 O(N)으로 시간 초과가 발생하게 된다.

 

2의 32승은 2의 16승 * 2의 16승과 같다. 이러한 지수 법칙을 이용하면 계산의 시간복잡도를 O(logN)으로 바꾸어 문제를 해결할 수 있다.

 

A*B % C = ((A%C) * (B%C)) % C 임을 이용해 오버플로우가 나지 않도록 계산을 진행하여야 한다.


소스 코드

import sys


def div_and_con(a, b, c):
    if b == 1:
        return a % c

    temp = div_and_con(a, b//2, c)

    if b % 2 == 0:
        return ((temp%c) * (temp%c)) % c
    else:
        return ((temp%c) * (temp%c) * (a%c)) % c


A, B, C = map(int, sys.stdin.readline().split())
print(div_and_con(A, B, C))

 

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

 

1043번: 거짓말

지민이는 파티에 가서 이야기 하는 것을 좋아한다. 파티에 갈 때마다, 지민이는 지민이가 가장 좋아하는 이야기를 한다. 지민이는 그 이야기를 말할 때, 있는 그대로 진실로 말하거나 엄청나게

www.acmicpc.net


풀이 과정

진실을 아는 사람, 진실을 들었던 사람들이 참가한 파티에서는 거짓말을 할 수 없고, 진실을 아는 사람과 파티에 참석하는 사람들의 번호가 주어질 때 거짓말을 할 수 있는 최대 횟수를 구하는 문제이다.

 

문제를 읽고 생각할 수 있는 것은

1. 파티에 진실을 아는 사람이 참석했으면 무조건 진실을 말해야 한다.

2. 진실을 아는 사람이 참석한 파티의 모든 참가자들은 진실을 듣게 되었으므로, 원래 진실을 몰랐던 사람도 아는 사람으로 바뀌게 된다.

 

라고 판단하고 코드를 짠 다음에 예시 입력을 넣어보면 예시 출력과 다른 답이 나오게 된다. 무엇이 문제일까?

 

1번이라는 사람이 진실을 알고, 첫 번째 파티에 3번이 참가, 두 번째 파티에 1번과 3번이 참가했다고 가정해보자. 첫 번째 파티는 진실을 모르는 3번이 참가했으니 거짓말 횟수 1 증가... 두 번째 파티는 진실을 아는 1번이 참가했으니 3번을 진실을 아는 사람으로 설정하고 거짓말 횟수 보류... 이러면 오답을 출력하게 된다. 최종적으로는 3번이 진실을 알게 되므로 첫 번째 파티에서 거짓말을 하면 3번에게 거짓이라는 것을 2번째 파티에서 들키게 된다.

 

파티 참가자들을 살펴보며 union-find를 사용해서 진실을 아는 사람, 진실을 들은 사람을 전부 다 연결시키고, 다시 한번 파티 참가자들을 살펴보면서 진실을 모르는 사람들만 있는 파티에서 거짓말 횟수를 증가시키면 문제를 해결할 수 있다.


소스 코드

import sys

parent = [i for i in range(51)]
truth_check = [False for _ in range(51)]


def find(a):
    if parent[a] == a:
        return a
    else:
        parent[a] = find(parent[a])
        return parent[a]


def uni(a, b):
    aRoot = find(a)
    bRoot = find(b)
    if aRoot == bRoot:
        return
    if truth_check[aRoot]:
        parent[bRoot] = aRoot
        return

    parent[aRoot] = bRoot


N, M = map(int, sys.stdin.readline().split())
truth_people = list(map(int, sys.stdin.readline().split()))
people_list = []

if truth_people[0] != 0:
    for i in range(1, truth_people[0] + 1):
        truth_check[truth_people[i]] = True

for _ in range(M):
    people = list(map(int, sys.stdin.readline().split()))
    people_list.append(people[1:])
    prev = people[1]

    for i in range(1, people[0] + 1):
        uni(prev, people[i])
        prev = people[i]

answer = 0

for peo in people_list:
    truth_flag = False
    for pe in peo:
        if truth_check[find(pe)]:
            truth_flag = True
    if not truth_flag:
        answer += 1

print(answer)

 

+ Recent posts