2096번: 내려가기
(소요시간 : 48분..)
1 2 3 -> 3을 선택
4 0 1 -> 4를 선택할 수가 없다. 또 이 줄의 max인 1선택하면
9 6 1 -> 여기서 또, 9를 선택할 수가 없다. 
0 7 8
하나의 선택은 어느 range까지에서의 min, max에 영향? -> 한 줄 
// 정답 22 7
각 줄에서는 index 0,1,2에 있는 것 중 하나를 선택하도록 한다.
현재 줄의 index에 +1,0,-1을 선택 할 수 있으며 → 이것이 0보다 작거나 2보다 크면 out of range 이므로 pass한다.
전체탐색처럼 생각해 보면, 결국 중복되는 subproblem들이 존재 한다.
이 경우, 
첫 줄에서 1을 선택할 때, 두 번째 줄에서 4,0 선택 가능
첫 줄에서 2를 선택할 때, 두 번째 줄에서 4,0,1 선택가능.
결국 다음 줄에서 선택한 것은 그 다음줄에만 영향을 주니
subproblem이 생긴다. 
이를 top down 방식으로 생각하되, 이미 방문한 것을
재방문 하지 않도록만 해 보자. 
1 2 3
4 0 1
9 6 
min 과 max 에 대한 것을 따로 해야하기 때문에, cache는 3차원으로 생성하였다.
이미 해당 좌표에 대한 min 을 구하기 위한 여정을 떠났다면( min을 구하기 위한 과정에서 해당 좌표를 이미 방문) 다시 방문 할 필요가 없다.
이미 해당 좌표에 대한 max 를 구하기 위한 여정을 떠났다면( min을 구하기 위한 과정에서 해당 좌표를 이미 방문) 다시 방문 할 필요가 없다.
재귀함수를 사용하는 방식의 DP는 잘 생각나는데, 그 외의 것을 계속해서 못 해내고 있다.. 그래서 bottom up 방식으로도 다시 하나 짜 보았다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
    public static int n;
    public static int[][] board = new int[100000][3];
    public static int[][][] cache = new int[100000][3][2];
    public static boolean[][][] visit = new boolean[100000][3][2];
    public static void Setting() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        n = Integer.parseInt(br.readLine());
        for(int i=0;i<n;i++){
            st = new StringTokenizer(br.readLine());
            for(int c=0;c<3;c++){
                board[i][c] = Integer.parseInt(st.nextToken());
            }
        }
        // setting cache
        for(int r=0;r<n;r++){
            for(int c=0;c<3;c++)
                Arrays.fill(cache[r][c],-1);
        }
    }
    // max 에 대해, min 에 대해 각각 해도 될 것 같다.
    // max는 cahce[r][c]의 0에 , min 은 1에 저장
    // target은 지금 구하는게 max인지 min인지를 나타낸다.
    public static int dp_max(int y,int x){
        if(y==n-1) return board[y][x];
        if(cache[y][x][0]!=-1)return cache[y][x][0];
        int ny=y+1,nx=0,max=0;
        for(int idx=-1;idx<2;idx++){
            // curx + idx
            nx = x +idx;
            if(nx<0||nx>2) continue;
            max = Math.max(max,dp_max(ny,nx));
        }
        max += board[y][x];
        return cache[y][x][0]=max;
    }
    public static int dp_min(int y, int x){
        if(y == n-1) return board[y][x];
        if(cache[y][x][1]!=-1) return cache[y][x][1];
        int ny =y+1,nx=0,min = Integer.MAX_VALUE; // 9 * 100000 = 90만
        for(int idx=-1;idx<2;idx++){
            nx = x +idx;
            if(nx<0 || nx>2) continue;
            min = Math.min(min,dp_min(ny,nx));
        }
        min += board[y][x];
        return cache[y][x][1]=min;
    }
    public static void main(String[] args) throws IOException {
        Setting();
        int max=0,min=Integer.MAX_VALUE;
        for(int idx=0;idx<3;idx++){
            min = Math.min(min,dp_min(0,idx));
            max = Math.max(max,dp_max(0,idx));
        }
        System.out.println(max+" "+min);
    }
}
for문을 사용한다.
공간 복잡도를 최소화 하기 위하여, cache 배열을 사용하지 않고, min, max 변수를 for문을 도는 동안 update하는 방식을 사용할 수 있다.
r행 c열 에 대해 구할 때, r-1행 c-1열 , r-1행 c열, r-1행 c+1열 에 더하는 것 중 가장 max값 , 또는 min 을 update한다.
package coding;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
    public static int n;
    public static int[][] board = new int[100000][3];
    public static void Setting() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        n = Integer.parseInt(br.readLine());
        for(int i=0;i<n;i++){
            st = new StringTokenizer(br.readLine());
            for(int c=0;c<3;c++){
                board[i][c] = Integer.parseInt(st.nextToken());
            }
        }
    }
    public static void main(String[] args) throws IOException {
        Setting();
        bottomUp();
    }
    private static void bottomUp() {
        // 마지막에 이 셋 중 max  -> real max
        // 마지막에 이 셋 중 min -> real min
        // mins[r][0] 은 pre (이전 row)
        // mins[r][1] 은 cur (현재 row) 
        int[][] mins = new int[2][3];
        int[][] maxs = new int[2][3];
        // INIT
        for(int c=0;c<3;c++) {
            mins[0][c] = board[0][c];
            maxs[0][c] = board[0][c];
        }
        // SOLVE
        for(int row=1;row<n;row++){
            // mins,max[0]을 구하기 위해서는  -> c와 c+1을
            mins[1][0] = Math.min(mins[0][0],mins[0][1])+board[row][0];
            maxs[1][0] = Math.max(maxs[0][0],maxs[0][1])+board[row][0];
            // mins,max[1]을 구하기 위해서는 -> c-1과 c와 c+1을
            mins[1][1] = Math.min(mins[0][0],mins[0][1]);
            mins[1][1] = Math.min(mins[1][1],mins[0][2]) + board[row][1];
            maxs[1][1] = Math.max(maxs[0][0],maxs[0][1]);
            maxs[1][1] = Math.max(maxs[1][1],maxs[0][2]) + board[row][1];
            // mins,max[2]를 구하기 위해서는 -> c-1과 c를
            mins[1][2] = Math.min(mins[0][1],mins[0][2]) + board[row][2];
            maxs[1][2] = Math.max(maxs[0][1],maxs[0][2]) + board[row][2];
            // 다시 pre와 cur을 바꾼다.
            for(int c=0;c<3;c++){
                mins[0][c]=mins[1][c];
                maxs[0][c]=maxs[1][c];
            }
        }
        // 셋 중 최대 최소 값을 구한다.
        int resmax=0,resmin = Integer.MAX_VALUE;
        for(int c=0;c<3;c++){
            resmax = Math.max(resmax,maxs[0][c]);
            resmin = Math.min(resmin,mins[0][c]);
        }
        System.out.println(resmax+" "+resmin);
    }
}