[코딩테스트] 브루트 포스(NM과 K)
reference : 2021 코딩테스트 기초(최백준) 강의를 공부하며 정리한 내용입니다.
- N과 M은 재귀에 많이 사용되기 때문에 중요하다.
- 브루트 포스에 재귀로 풀 수 있는 문제
- 순서 : N가지 M개 뽑을 때 순서가 중요한 문제 O(N!)
- 선택 : N가지를 다 할 수 있는데 M개를 어떤 순서로 할지 O(2ⁿ)
NM과 K (18290번)
https://www.acmicpc.net/problem/18290
문제
칸에 정수가 하나씩 들어있는 N행 M열의 격자판에서 K개의 칸을 선택하는 문제(선택한 칸이 인접하면 안 된다), 선택한 칸에 들어있는 수의 합을 최대로 하는 문제
1 ≤ N, M ≤ 10, 1 ≤ K ≤ min(4, N×M)
입력
첫째 줄에 N, M, K가 주어진다. 둘째 줄부터 N개의 줄에 격자판에 들어있는 수가 주어진다.
출력
선택한 칸에 들어있는 수를 모두 더한 값의 최댓값을 출력한다.
풀이
- N과 M은 1차원 문제라면 NM은 2차원. NM개 중에 최대 4개 100C₄ = 100x99x98x97/4x3x2x1 = 3,921,225가지
import java.util.*;
public class Main {
static int n,m,k;
static int[][] a = new int[10][10];
static boolean[][] c = new boolean[10][10];
static int[] dx = {0, 0, 1, -1};
static int[] dy = {1, -1, 0, 0};
static int ans = Integer.MIN_VALUE;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
k = sc.nextInt();
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
a[i][j] = sc.nextInt();
}
}
go(0,0);
System.out.println(ans);
}
private static void go(int cnt, int sum) {
// int cnt : 선택한 칸의 개수
// int sum : 선택한 값의 합
if(cnt==k) {
if(ans<sum)
ans = sum;
return;
}
for(int x=0; x<n; x++) {
for(int y=0; y<m; y++) {
if(c[x][y]) {
continue;
}
boolean ok = true; // 선택가능 true, 아니면 false
for(int i=0; i<4; i++) {
int nx = x+dx[i];
int ny = y+dy[i];
//인접한 4방향 검사
if(0<=nx && nx<n && 0<=ny && ny<m) {
// 인접한 칸이 문제 격자판 범위 안에 있고
if(c[nx][ny]) // 그 칸을 선택한 적이 있으면
ok = false; // 선택할 수 없다.
}
}
if(ok) {
c[x][y] = true;
go(cnt+1, sum+a[x][y]);
c[x][y] = false;
}
}
}
}
}
- 함수 호출 최대 4번, 각각 함수마다 nm 호출, O((NM)⁴) = 1억
- 위 풀이로는 선택한 칸이 같은데, 선택한 순서가 다른 방법을 여러 번 계산하게 된다. (중복선택)
- 중복선택을 피하기 위해선 N과 M 2번쨰에서 오름차순을 위해 start를 써줬듯이 얘도 비슷하게 활용. (2,5)를 구했다면 (1,1)~(2,5)까진 계산팔 필요가 없으므로 (2,6)~(N,M)까지만 계산. 선택한 칸의 행을 항상 오름차순으로 유지한다면 중복된 선택 피할 수 있음.
private static void go(int px, int cnt, int sum) { //px : prev_x 이전 선택칸 행번호
if(cnt==k) {
if(ans<sum)
ans = sum;
return;
}
for(int x=px; x<n; x++) { //x=px
for(int y=0; y<m; y++) {
if(c[x][y]) {
continue;
}
boolean ok = true;
for(int i=0; i<4; i++) {
int nx = x+dx[i];
int ny = y+dy[i];
if(0<=nx && nx<n && 0<=ny && ny<m) {
if(c[nx][ny])
ok = false;
}
}
if(ok) {
c[x][y] = true;
go(x, cnt+1, sum+a[x][y]); //다음함수는 x번부터 검사하라
c[x][y] = false;
}
}
}
}
- 같은 행에서 중복된 선택을 할 수 있다는 단점. (1,2) (2,1) 선택은 피했지만 (1,2)(1,5) 후에 (1,5)(1,2)는 못피함. 이걸 위해서 go에 int py를 추가해주자.
px==x
면y=py+1
,px>x
면y=1
부터 시작 - 속도가 처음 코드 1628ms 에서 360ms로 훨씬 단축된다!
- 또는 (r,c)는 r x M + c로 나타낼 수도 있다. row-major-order(https://blog.naver.com/loveyou_a_a/222136576687 참고). 1차원으로 가정하고 문제해결. N과 M(2)와 같이 풀면 된다. (x,y 좌표를 지정할 때 x는 m으로 나눈 몫으로, y는 m으로 나눈 나머지로)
댓글남기기