diff --git a/problems/SWEA/p1218/Solution.java b/problems/SWEA/p1218/Solution.java new file mode 100644 index 0000000..58e87e3 --- /dev/null +++ b/problems/SWEA/p1218/Solution.java @@ -0,0 +1,128 @@ +/* + * (1218) [S/W 문제해결 기본] 4일차 - 괄호 짝 짓기 + * https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV14eWb6AAkCFAYD&categoryId=AV14eWb6AAkCFAYD&categoryType=CODE&problemTitle=1218&orderBy=FIRST_REG_DATETIME&selectCodeLang=ALL&select-1=&pageSize=10&pageIndex=1 + */ + +package swea.p1218; + +import java.io.*; +import java.util.*; + +/** + * SW Expert Academy - 1218. [S/W 문제해결 기본] 4일차 - 괄호 짝 짓기 + * @author YeJun, Jung + * + * @see #main(String[]) + * 1. 입출력을 초기화한다. + * 2. 테스트 케이스 10개를 반복하여 실행한다. + * + * @see #Solution(int) + * 3. 멤버 변수를 초기화한다. + * + * @see #run() + * 4. 입력, 해결, 출력 순서로 실행한다. + * + * @see #input() + * 5. 문자열의 길이를 입력받으나 사용하지 않으므로 무시한다. + * 6. 검사할 괄호 문자열을 입력받아 chars 배열에 저장한다. + * + * @see #solve() + * 7. 정답을 0(유효하지 않음)으로 초기화한다. + * 8. Deque를 활용하여 스택(stack) 객체를 생성한다. + * 9. 문자열의 각 문자를 순회하며 유효성을 검사한다. + * 9-1. 닫는 괄호일 경우, 스택의 top이 대응하는 여는 괄호인지 확인한다. + * 9-2. 일치하지 않으면 유효하지 않은 문자열로 판단하고 종료한다. + * 9-3. 여는 괄호일 경우, 스택에 push한다. + * 10. 모든 검사를 통과하면 정답을 1(유효함)로 설정한다. + * + * @see #print() + * 11. 결과값을 양식에 맞춰 출력한다. + */ +public class Solution { + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); + + public static void main(String[] args) throws IOException { + // 1. 입출력을 초기화한다. + final int testCount = 10; + + // 2. 테스트 케이스 10개를 반복하여 실행한다. + for (int testCase = 1; testCase <= testCount; testCase++) { + new Solution(testCase).run(); + } + } + + // -------------------------------------------------------- + + private int testCase; + private char answer; + private char[] chars; + + public Solution(int testCase) { + // 3. 멤버 변수를 초기화한다. + this.testCase = testCase; + } + + public void run() throws IOException { + // 4. 입력, 해결, 출력 순서로 실행한다. + input(); + solve(); + print(); + } + + private void input() throws IOException { + // 5. 문자열의 길이를 입력받으나 사용하지 않으므로 무시한다. + reader.readLine(); + + // 6. 검사할 괄호 문자열을 입력받아 chars 배열에 저장한다. + chars = reader.readLine().trim().toCharArray(); + } + + private void solve() { + // 7. 정답을 0(유효하지 않음)으로 초기화한다. + answer = '0'; + + // 8. Deque를 활용하여 스택(stack) 객체를 생성한다. + Deque stack = new ArrayDeque<>(); + + // 9. 문자열의 각 문자를 순회하며 유효성을 검사한다. + for (char ch : chars) { + Character peek = stack.peek(); + + switch (ch) { + case ')': + // 9-1. 닫는 괄호일 경우, 스택의 top이 대응하는 여는 괄호인지 확인한다. + if (peek != '(') return; // 9-2. 일치하지 않으면 유효하지 않은 문자열로 판단 + stack.pop(); + break; + case ']': + if (peek != '[') return; + stack.pop(); + break; + case '}': + if (peek != '{') return; + stack.pop(); + break; + case '>': + if (peek != '<') return; + stack.pop(); + break; + default: + // 9-3. 여는 괄호일 경우, 스택에 push한다. + stack.push(ch); + } + } + + // 10. 모든 검사를 통과하면 정답을 1(유효함)로 설정한다. + answer = '1'; + } + + private void print() throws IOException { + // 11. 결과값을 양식에 맞춰 출력한다. + writer.write("#" + testCase); + writer.write(" "); + writer.write(answer); + writer.write("\n"); + writer.flush(); + } +} \ No newline at end of file diff --git a/problems/SWEA/p1873/Solution.java b/problems/SWEA/p1873/Solution.java new file mode 100644 index 0000000..bfda98d --- /dev/null +++ b/problems/SWEA/p1873/Solution.java @@ -0,0 +1,203 @@ +/* + * (1873) 상호의 배틀필드 + * https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5LyE7KD2ADFAXc&categoryId=AV5LyE7KD2ADFAXc&categoryType=CODE&problemTitle=1873&orderBy=FIRST_REG_DATETIME&selectCodeLang=ALL&select-1=&pageSize=10&pageIndex=1 + */ + +package swea.p1873; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.StringTokenizer; + +/** + * SW Expert Academy - 1873. 상호의 배틀필드 + * @author YeJun, Jung + * + * @see #main(String[]) + * 1. 입출력을 초기화한다. + * 2. 테스트 케이스 개수를 입력받는다. + * 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + * + * @see #Solution(int) + * 4. 멤버 변수를 초기화한다. + * + * @see #run() + * 5. 입력, 해결, 출력 순서로 실행한다. + * + * @see #input() + * 6. 맵의 높이(H)와 너비(W)를 입력받는다. + * 7. 맵 정보를 입력받으며 플레이어의 초기 위치를 저장한다. + * 8. 명령어 배열을 입력받는다. + * + * @see #solve() + * 9. 입력받은 명령어를 순차적으로 수행한다. + * + * @see #doShoot() + * 10. 현재 바라보는 방향으로 포탄을 발사한다. + * 10-1. 벽돌 벽(*)을 만나면 평지로 만들고 소멸한다. + * 10-2. 강철 벽(#)을 만나면 아무 일 없이 소멸한다. + * + * @see #doMove(char) + * 11. 명령에 따라 방향을 바꾸고 이동 가능 시 이동한다. + * + * @see #print() + * 12. 최종 상태의 맵을 출력한다. + */ +public class Solution { + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); + static StringTokenizer input; + + public static void main(String[] args) throws IOException { + // 1. 입출력을 초기화한다. + + // 2. 테스트 케이스 개수를 입력받는다. + final int testCount = Integer.parseInt(reader.readLine().trim()); + + for (int testCase = 1; testCase <= testCount; testCase++) { + // 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + new Solution(testCase).run(); + } + } + + // -------------------------------------------------------- + + private int testCase; + + private int height; + private int width; + private char[][] board; + private char[] cmds; + + private int playerX; + private int playerY; + + public Solution(int testCase) { + // 4. 멤버 변수를 초기화한다. + this.testCase = testCase; + } + + public void run() throws IOException { + // 5. 입력, 해결, 출력 순서로 실행한다. + input(); + solve(); + print(); + } + + private void input() throws IOException { + // 6. 맵의 높이(H)와 너비(W)를 입력받는다. + getLine(); + height = Integer.parseInt(input.nextToken()); + width = Integer.parseInt(input.nextToken()); + + // 7. 맵 정보를 입력받으며 플레이어의 초기 위치를 저장한다. + board = new char[height][width]; + for (int y = 0; y < height; y++) { + board[y] = reader.readLine().trim().toCharArray(); + + for (int x = 0; x < width; x++) { + if (board[y][x] < '<') continue; // 아스키 코드상 .*#- 는 ^v<> 보다 작다 + + playerX = x; + playerY = y; + } + } + + // 8. 명령어 배열을 입력받는다. + reader.readLine(); // 명령어의 개수(해당 라인의 입력은 무시한다) + cmds = reader.readLine().trim().toCharArray(); + } + + private void solve() { + // 9. 입력받은 명령어를 순차적으로 수행한다. + for (char cmd : cmds) { + if (cmd == 'S') doShoot(); + else doMove(cmd); + } + } + + private void doShoot() { + // 10. 현재 바라보는 방향으로 포탄을 발사한다. + int bulletX = playerX; + int bulletY = playerY; + + int deltaX = 0; + int deltaY = 0; + + switch (board[playerY][playerX]) { + case '^': deltaY--; break; + case 'v': deltaY++; break; + case '<': deltaX--; break; + case '>': deltaX++; break; + } + + boolean isContinue = true; + while (isContinue && isInsideBoard(bulletX + deltaX, bulletY + deltaY)) { + bulletX += deltaX; + bulletY += deltaY; + + switch (board[bulletY][bulletX]) { + case '*': // 10-1. 벽돌 벽(*)을 만나면 평지로 만들고 소멸한다. + board[bulletY][bulletX] = '.'; + case '#': // 10-2. 강철 벽(#)을 만나면 아무 일 없이 소멸한다. + isContinue = false; + break; + } + } + } + + private void doMove(char dir) { + // 11. 명령에 따라 방향을 바꾸고 이동 가능 시 이동한다. + int nextX = playerX; + int nextY = playerY; + char icon = board[playerY][playerX]; + + switch (dir) { + case 'U': nextY--; icon = '^'; break; + case 'D': nextY++; icon = 'v'; break; + case 'L': nextX--; icon = '<'; break; + case 'R': nextX++; icon = '>'; break; + } + + board[playerY][playerX] = icon; + + if (isInsideBoard(nextX, nextY) && isMovable(nextX, nextY)) { + board[nextY][nextX] = board[playerY][playerX]; + board[playerY][playerX] = '.'; + + playerX = nextX; + playerY = nextY; + } + } + + private boolean isInsideBoard(int x, int y) { + return x >= 0 && x < width && y >= 0 && y < height; + } + + private boolean isMovable(int x, int y) { + return board[y][x] == '.'; + } + + private void print() throws IOException { + // 12. 최종 상태의 맵을 출력한다. + writer.write("#" + testCase + " "); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + writer.write(board[y][x]); + } + writer.write("\n"); + } + + writer.flush(); + } + + // -------------------------------------------------------- + + private static void getLine() throws IOException { + input = new StringTokenizer(reader.readLine().trim()); + } +} \ No newline at end of file diff --git a/problems/SWEA/p2115/Solution.java b/problems/SWEA/p2115/Solution.java new file mode 100644 index 0000000..77fc09c --- /dev/null +++ b/problems/SWEA/p2115/Solution.java @@ -0,0 +1,269 @@ +/* + * (2115) [모의 SW 역량테스트] 벌꿀채취 + * https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5V4A46AdIDFAWu&categoryId=AV5V4A46AdIDFAWu&categoryType=CODE&problemTitle=2115&orderBy=FIRST_REG_DATETIME&selectCodeLang=ALL&select-1=&pageSize=10&pageIndex=1 + */ + +package swea.p2115; + +import java.io.*; +import java.util.*; + +/** + * SW Expert Academy - 2115. [모의 SW 역량테스트] 벌꿀채취 + * @author YeJun, Jung + * + * @see #main(String[]) + * 1. 입출력을 초기화한다. + * 2. 테스트 케이스 개수를 입력받는다. + * 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + * + * @see #Solution(int) + * 4. 멤버 변수를 초기화한다. + * + * @see #run() + * 5. 입력, 해결, 출력 순서로 실행한다. + * + * @see #input() + * 6. 벌통 크기(N), 선택 벌통 수(M), 꿀 채취 제한량(C)을 입력받는다. + * 7. 벌통의 꿀 정보를 board 배열에 저장한다. + * + * @see #solve() + * 8. 두 명의 일꾼(Cursor)을 생성하여 각각 최적의 위치를 찾는다. + * + * @see #searchBest(int) + * 9. 현재 일꾼이 다른 일꾼과 겹치지 않는 위치 중 최대 이익을 낼 수 있는 곳을 탐색한다. + * 10. Cursor를 이동시키며 이익을 계산한다. + * 11. 탐색된 최적 위치를 일꾼 객체에 저장하고 전체 정답에 합산한다. + * + * @see #getBestFromCursor(Cursor) + * 12. 선택된 벌통 범위 내에서 꿀의 합이 C를 넘지 않는 부분 집합의 최대 이익을 계산한다. + * 12-1. 부분 집합 구성을 위해 정렬 및 그리디/이진 탐색 기법을 활용한다. + * 13. 선택된 벌통들의 제곱의 합(이익)을 계산하여 반환한다. + * + * @see #print() + * 14. 계산된 최대 이익(answer)을 출력한다. + */ +public class Solution { + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); + static StringTokenizer input; + + public static void main(String[] args) throws IOException { + // 1. 입출력을 초기화한다. + + // 2. 테스트 케이스 개수를 입력받는다. + int testCount = Integer.parseInt(reader.readLine().trim()); + + for (int testCase = 1; testCase <= testCount; testCase++) { + // 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + new Solution(testCase).run(); + } + } + + // -------------------------------------------------------- + + private int testCase; + private int answer; + + private int boardSize; + private int cursorSize; + private int maxValue; + + private int[][] board; + private Cursor[] cursors; + + public Solution(int testCase) { + // 4. 멤버 변수를 초기화한다. + this.testCase = testCase; + } + + public void run() throws IOException { + // 5. 입력, 해결, 출력 순서로 실행한다. + input(); + solve(); + print(); + } + + private void input() throws IOException { + // 6. 벌통 크기(N), 선택 벌통 수(M), 꿀 채취 제한량(C)을 입력받는다. + getLine(); + boardSize = Integer.parseInt(input.nextToken()); + cursorSize = Integer.parseInt(input.nextToken()); + maxValue = Integer.parseInt(input.nextToken()); + + // 7. 벌통의 꿀 정보를 board 배열에 저장한다. + board = new int[boardSize][boardSize]; + for (int y = 0; y < boardSize; y++) { + getLine(); + for (int x = 0; x < boardSize; x++) { + board[y][x] = Integer.parseInt(input.nextToken()); + } + } + } + + private void solve() { + answer = 0; + + // 8. 두 명의 일꾼(Cursor)을 생성하여 각각 최적의 위치를 찾는다. + cursors = new Cursor[2]; + cursors[0] = new Cursor(); + cursors[1] = new Cursor(); + + for (int index = 0; index < cursors.length; index++) { + searchBest(index); + } + } + + private void searchBest(int cursorIndex) { + // 9. 현재 일꾼이 다른 일꾼과 겹치지 않는 위치 중 최대 이익을 낼 수 있는 곳을 탐색한다. + Cursor myCursor = cursors[cursorIndex]; + Cursor anotherCursor = cursors[(cursorIndex + 1) % 2]; + + int bestValue = 0; + + for (int y = 0; y < boardSize; y++) { + // 10. Cursor를 이동시키며 이익을 계산한다. + Cursor cursor = new Cursor(y, 0, cursorSize - 1); + + for (int x = 0; x <= boardSize - cursorSize; x++) { + if (!cursor.isOverlap(anotherCursor)) { + int current = getBestFromCursor(cursor); + + if (current > bestValue) { + bestValue = current; + // 11. 탐색된 최적 위치를 일꾼 객체에 저장하고 전체 정답에 합산한다. + myCursor.set(cursor); + } + } + cursor.moveRight(); + } + } + + answer += bestValue; + } + + private int getBestFromCursor(Cursor cursor) { + // 12. 선택된 벌통 범위 내에서 꿀의 합이 C를 넘지 않는 부분 집합의 최대 이익을 계산한다. + final int N = cursor.xEnd() - cursor.xBegin() + 1; + + int[] buffer = new int[N]; + System.arraycopy(board[cursor.y()], cursor.xBegin(), buffer, 0, N); + Arrays.sort(buffer); + + int best = 0; + + // 12-1. 부분 집합 구성을 위해 정렬 및 그리디/이진 탐색 기법을 활용한다. + for (int index = N - 1; index >= 0; index--) { + List candidates = new ArrayList<>(); + candidates.add(buffer[index]); + + int value = buffer[index]; + int begin = 0, end = index; + + while (end > 0 && candidates.size() < N && value < maxValue) { + int nextIndex = binarySearchOrLower(buffer, begin, end, maxValue - value); + int nextValue = buffer[nextIndex]; + + if (value + nextValue > maxValue) break; + + value += nextValue; + candidates.add(nextValue); + + end = nextIndex; + } + + // 13. 선택된 벌통들의 제곱의 합(이익)을 계산하여 반환한다. + int currentBenefit = calcBenefit(candidates); + if (currentBenefit > best) { + best = currentBenefit; + } + } + + return best; + } + + private int calcBenefit(List values) { + int benefit = 0; + for (int value : values) benefit += value * value; + return benefit; + } + + private void print() throws IOException { + // 14. 계산된 최대 이익(answer)을 출력한다. + writer.write("#" + testCase + " " + answer + "\n"); + writer.flush(); + } + + // -------------------------------------------------------- + + private static class Cursor { + private int y; + private int xBegin; + private int xEnd; + + public Cursor() { this(-1, -1, -1); } + public Cursor(Cursor c) { this(c.y(), c.xBegin(), c.xEnd()); } + public Cursor(int y, int xBegin, int xEnd) { + this.y = y; + this.xBegin = xBegin; + this.xEnd = xEnd; + } + + public int y() { return y; } + public int xBegin() { return xBegin; } + public int xEnd() { return xEnd; } + + public boolean isOverlap(Cursor c) { + if (this.y != c.y()) return false; + + boolean result = false; + result |= (this.xBegin > c.xBegin() && this.xBegin > c.xEnd()); + result |= (this.xEnd < c.xBegin() && this.xEnd < c.xEnd()); + return !result; + } + + public void moveRight() { + xBegin++; xEnd++; + } + + public void set(Cursor c) { + this.y = c.y(); + this.xBegin = c.xBegin(); + this.xEnd = c.xEnd(); + } + + @Override + public String toString() { + return String.format("[y=%d, xBegin=%d, xEnd=%d]", y, xBegin, xEnd); + } + } + + // -------------------------------------------------------- + + private static void getLine() throws IOException { + input = new StringTokenizer(reader.readLine().trim()); + } + + private static int binarySearchOrLower(int[] x, int begin, int end, int target) { + int index = 0, value = 0; + + // binary search + while(begin < end) { + index = (begin + end) / 2; + value = x[index]; + + if (value < target) { + begin = index + 1; + } else if (value > target) { + end = index - 1; + } else { + break; + } + } + + // or lower + if (value > target && index > 0) index--; + + return index; + } +} \ No newline at end of file diff --git a/problems/SWEA/p5215/Solution.java b/problems/SWEA/p5215/Solution.java new file mode 100644 index 0000000..d5035a0 --- /dev/null +++ b/problems/SWEA/p5215/Solution.java @@ -0,0 +1,159 @@ +/* + * (5215) 햄버거 다이어트 + * https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWT-lPB6dHUDFAVT&categoryId=AWT-lPB6dHUDFAVT&categoryType=CODE&problemTitle=5215&orderBy=FIRST_REG_DATETIME&selectCodeLang=ALL&select-1=&pageSize=10&pageIndex=1 + */ + +package swea.p5215; + +import java.io.*; +import java.util.*; +import java.util.stream.IntStream; + +/** + * SW Expert Academy - 5215. 햄버거 다이어트 + * @author YeJun, Jung + * + * @see #main(String[]) + * 1. 입출력을 초기화한다. + * 2. 테스트 케이스 개수를 입력받는다. + * 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + * + * @see #Solution(int) + * 4. 멤버 변수를 초기화한다. + * + * @see #run() + * 5. 입력, 해결, 출력 순서로 실행한다. + * + * @see #input() + * 6. 재료의 수(partCount)와 제한 칼로리(limit)를 입력받는다. + * 7. 각 재료의 점수와 칼로리 정보를 배열에 저장한다. + * + * @see #solve() + * 8. 최적 점수(bestScore)를 초기화한다. + * 9. 선택할 재료의 개수를 1개부터 N개까지 늘려가며 조합을 생성한다. + * 10. nextPermutation을 이용하여 현재 선택 개수(count)에 대한 모든 조합을 탐색한다. + * 10-1. 선택된 재료들의 칼로리 합과 점수 합을 계산한다. + * 10-2. 제한 칼로리 미만이며 기존 최고 점수보다 높을 경우 갱신한다. + * 11. 다음 개수의 조합 생성을 위해 선택 배열(selects)을 초기화(reverse)한다. + * + * @see #print() + * 12. 최종적으로 계산된 최대 점수를 출력한다. + */ +public class Solution { + // 1. 입출력을 초기화한다. + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); + static StringTokenizer input; + + public static void main(String[] args) throws IOException { + // 2. 테스트 케이스 개수를 입력받는다. + final int testCount = Integer.parseInt(reader.readLine().trim()); + + for (int testCase = 1; testCase <= testCount; testCase++) { + // 3. 각 테스트 케이스에 대해 솔루션을 실행한다. + new Solution(testCase).run(); + } + } + + // ---------------------------------------------------------- + + private int testCase; + private int bestScore; + + private int partCount; + private int limit; + private int[] parts; + private int[] scores; + + public Solution(int testCase) { + // 4. 멤버 변수를 초기화한다. + this.testCase = testCase; + } + + public void run() throws IOException { + // 5. 입력, 해결, 출력 순서로 실행한다. + input(); + solve(); + print(); + } + + private void input() throws IOException { + // 6. 재료의 수(partCount)와 제한 칼로리(limit)를 입력받는다. + getLine(); + partCount = Integer.parseInt(input.nextToken()); + limit = Integer.parseInt(input.nextToken()); + + // 7. 각 재료의 점수와 칼로리 정보를 배열에 저장한다. + scores = new int[partCount]; + parts = new int[partCount]; + + for (int index = 0; index < partCount; index++) { + getLine(); + scores[index] = Integer.parseInt(input.nextToken()); + parts[index] = Integer.parseInt(input.nextToken()); + } + } + + private void solve() { + // 8. 최적 점수(bestScore)를 초기화한다. + bestScore = 0; + int[] selects = new int[partCount]; + + // 9. 선택할 재료의 개수를 1개부터 N개까지 늘려가며 조합을 생성한다. + for (int count = 0; count < partCount; count++) { + selects[count] = 1; + + // 10. nextPermutation을 이용하여 현재 선택 개수에 대한 모든 조합을 탐색한다. + do { + int currentValue = 0; + int currentScore = 0; + + // 10-1. 선택된 재료들의 칼로리 합과 점수 합을 계산한다. + for (int index = 0; index < partCount; index++) { + if (selects[index] == 0) continue; + currentValue += parts[index]; + currentScore += scores[index]; + } + + // 10-2. 제한 칼로리 미만이며 기존 최고 점수보다 높을 경우 갱신한다. + if (currentValue < limit && currentScore > bestScore) { + bestScore = currentScore; + } + } while (nextPermutation(selects)); + + // 11. 다음 개수의 조합 생성을 위해 선택 배열(selects)을 초기화(reverse)한다. + reverse(selects, 0, partCount - 1); + } + } + + private void print() throws IOException { + // 12. 최종적으로 계산된 최대 점수를 출력한다. + writer.write("#" + testCase); + writer.write(" " + bestScore); + writer.write("\n"); + writer.flush(); + } + + // ---------------------------------------------------------- + + private static void getLine() throws IOException { + input = new StringTokenizer(reader.readLine().trim()); + } + + private static boolean nextPermutation(int[] x) { + int n = x.length - 1, a = n, b = n; + while (a > 0 && x[a - 1] <= x[a]) a--; + if (a == 0) return false; + while (x[a - 1] <= x[b]) b--; + swap(x, a - 1, b); reverse(x, a, n); + return true; + } + + private static void swap(int[] x, int a, int b) { + int t = x[a]; x[a] = x[b]; x[b] = t; + } + + private static void reverse(int[] x, int a, int b) { + while (a < b) swap(x, a++, b--); + } +} diff --git a/problems/baekjoon/p15650/Main.java b/problems/baekjoon/p15650/Main.java new file mode 100644 index 0000000..35aac12 --- /dev/null +++ b/problems/baekjoon/p15650/Main.java @@ -0,0 +1,103 @@ +/* + * (15650) N과 M (2) + * https://www.acmicpc.net/problem/15650 + */ + +package baekjoon.p15650; + +import java.io.*; +import java.util.*; +import java.util.stream.IntStream; + +/** + * Baekjoon - N과 M (2) + * @author YeJun, Jung + * + * @see #main(String[]) + * 1. 입출력을 초기화한다. + * 2. N과 M을 입력받는다. + * 3. 1부터 N까지의 숫자로 구성된 배열을 생성한다. + * 4. 반복문을 통해 순열을 생성하며 조건을 확인한다. + * 4-1. 선택된 M개의 숫자가 오름차순인지 확인한다. + * 4-2. 오름차순일 경우 해당 숫자들을 출력한다. + * 4-3. 중복 계산을 피하기 위해 배열의 뒷부분을 뒤집고 다음 순열로 넘어간다. + * + * @see #nextPermutation(int[]) + * 5. 현재 배열을 사전순으로 다음에 오는 순열로 변형한다. + * 5-1. 뒤에서부터 감소하는 지점(a-1)을 찾는다. + * 5-2. 다시 뒤에서부터 x[a-1]보다 큰 값(b)을 찾아 교환한다. + * 5-3. a 지점부터 끝까지 배열을 뒤집어 사전순 최소로 만든다. + * + * @see #reverse(int[], int, int) + * 6. 지정된 범위 내의 배열 요소 순서를 반전시킨다. + */ +public class Main { + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); + static StringTokenizer input; + + public static void main(String[] args) throws IOException { + // 1. 입출력을 초기화한다. + getLine(); + + // 2. N과 M을 입력받는다. + final int N = Integer.parseInt(input.nextToken()); + final int M = Integer.parseInt(input.nextToken()); + + // 3. 1부터 N까지의 숫자로 구성된 배열을 생성한다. + int[] arr = IntStream.rangeClosed(1, N).toArray(); + + // 4. 반복문을 통해 순열을 생성하며 조건을 확인한다. + do { + // 4-1. 선택된 M개의 숫자가 오름차순인지 확인한다. + int index = 1; + while (index < M && arr[index - 1] <= arr[index]) index++; + + // 4-2. 오름차순일 경우 해당 숫자들을 출력한다. + if (index == M) { + for (index = 0; index < M; index++) { + writer.write(arr[index] + " "); + } + writer.write("\n"); + } + + // 4-3. 중복 계산을 피하기 위해 배열의 뒷부분을 뒤집고 다음 순열로 넘어간다. + // (조합을 구하는 문제이므로 M 이후의 순서는 중요하지 않음) + reverse(arr, M, N - 1); + } while (nextPermutation(arr)); + + writer.flush(); + } + + // -------------------------------------------------------- + + private static void getLine() throws IOException { + input = new StringTokenizer(reader.readLine().trim()); + } + + private static boolean nextPermutation(int[] x) { + // 5. 현재 배열을 사전순으로 다음에 오는 순열로 변형한다. + int n = x.length - 1, a = n, b = n; + + // 5-1. 뒤에서부터 감소하는 지점(a-1)을 찾는다. + while (a > 0 && x[a - 1] >= x[a]) a--; + if (a == 0) return false; + + // 5-2. 다시 뒤에서부터 x[a-1]보다 큰 값(b)을 찾아 교환한다. + while (x[a - 1] >= x[b]) b--; + swap(x, a - 1, b); + + // 5-3. a 지점부터 끝까지 배열을 뒤집어 사전순 최소로 만든다. + reverse(x, a, n); + return true; + } + + private static void swap(int[] x, int a, int b) { + int t = x[a]; x[a] = x[b]; x[b] = t; + } + + private static void reverse(int[] x, int a, int b) { + // 6. 지정된 범위 내의 배열 요소 순서를 반전시킨다. + while (a < b) swap(x, a++, b--); + } +} \ No newline at end of file