2022.02.08
이 문제는, 여러 장들이 “연속이 되도록 파일을 합쳐” 야 한다.
이전과 다른 풀이로 풀었는데(풀리긴 했음), 시간이 더 걸렸어서
import java.io.*;
import java.util.*;
public class Main{
public static int[] input; // 현재 테스트의 input
public static int k; // 현재 테스트의 장의 개수
public static int[][] dp = new int[500][500];
public static int[][] sumCache = new int[500][500];
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StringTokenizer st;
public static void settingNSolve() throws IOException {
int t = Integer.parseInt(br.readLine());
// 각 테스트는 두 개의 행으로 주어진다.
for(int i = 0 ; i < t ; i ++){
// init
k = Integer.parseInt(br.readLine());
input = new int[k];
// init dp
for(int j=0;j<k;j++){
Arrays.fill(dp[j],-1);
}
st = new StringTokenizer(br.readLine());
for (int j = 0; j < k; j++) {
input[j] = Integer.parseInt(st.nextToken());
dp[j][j] = input[j];
sumCache[j][j] = input[j];
}
// init sumCache
sum();
System.out.println(recur(0,k-1));
}
}
public static int recur(int left, int right){
if(dp[left][right]!=-1)return dp[left][right];
//원소가 2개 인 경우
if(right-left == 1){
// System.out.println("[ "+left+","+right+" ] MIN : " +(input[left]+input[right]));
return dp[left][right] = (input[left]+input[right]);
}
int tempSum = 0, min = Integer.MAX_VALUE;
for(int i = left+1;i <= right;i++){
tempSum = recur(left,i-1);
tempSum += recur(i,right);
// 범위 길이가 1보다 크면 sum 을 더해줘야할 듯!!!!!
if( i-1-left > 0) tempSum += sumCache[left][i-1];
if( right - i > 0) tempSum += sumCache[i][right];
min = Math.min(min,tempSum);
}
// System.out.println("[ "+left+","+right+" ] MIN : " +(min));
return dp[left][right] = min;
}
public static void sum(){
for(int l = 0 ; l<k;l++){
for(int r = l+1; r<k;r++){
sumCache[l][r] = sumCache[l][r-1] + input[r];
}
}
}
public static void main(String[] args) throws IOException{
settingNSolve();
}
}
이전에 풀었던 방식을 다시 읽어보았다.
dp에 -1이 아닌 값이 있는지 확인
-1이면, 두 원소의 합을 dp에 세팅하고 리턴한다.
를 수행하도록 하였다.
그러다보니, 이 역시 recur을 호출할 때 매 번확인하는 if문이 하나 더 추가되는 것이 되어있었다.
2개의 원소로만 이루어진 서브셋의 값의 경우에는, 초기에 미리 dp를 세팅해 놓을 수 있었다.
항상 left <=right 이도록 하였기 때문에 sumCache의 초기화를 아래와같이 구현함
import java.io.*;
import java.util.*;
public class Main{
public static int[] input; // 현재 테스트의 input
public static int k; // 현재 테스트의 장의 개수
public static int[][] dp = new int[500][500];
public static int[][] sumCache = new int[500][500];
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StringTokenizer st;
public static void settingNSolve() throws IOException {
int t = Integer.parseInt(br.readLine());
// 각 테스트는 두 개의 행으로 주어진다.
for(int i = 0 ; i < t ; i ++){
// init
k = Integer.parseInt(br.readLine());
input = new int[k];
// init dp : 기본값 -1 , [i][i] 는 0 을 세팅
for(int j=0;j<k;j++){
Arrays.fill(dp[j],-1);
}
st = new StringTokenizer(br.readLine());
for (int j = 0; j <k; j++) {
input[j] = Integer.parseInt(st.nextToken());
dp[j][j] = 0;
sumCache[j][j] = input[j];
}
// 원소가 2개인 경우 dp 에 미리 세팅하기
for(int l = 0; l < k-1;l++){
dp[l][l+1] = input[l] + input[l+1];
}
// init sumCache
initSumCache();
// solve
System.out.println(recur(0,k-1));
}
}
public static int recur(int left, int right){
if(dp[left][right]!=-1) return dp[left][right];
int min = Integer.MAX_VALUE;
for(int i = left+1;i <= right;i++){
min = Math.min(min, recur(left,i-1)+recur(i,right));
}
min += sumCache[left][right];
return dp[left][right] = min;
}
public static void initSumCache(){
for(int l = 0 ; l<k;l++){
for(int r = l+1; r<k;r++){
sumCache[l][r] = sumCache[l][r-1] + input[r];
}
}
}
public static void main(String[] args) throws IOException{
settingNSolve();
}
}
C1 C2 C3 C4
40 30 30 50
C2 + C3 = X1 = 60
C1 + X1 = X2 =40 + 60 = 100
C4 + X2 = 50 + 100 = 150
또는
C1+ C2 = Y1 = 70
Y1 + C3 = Y2 = 70 + 30 = 100
C4 + Y2 = 100 + 50 = 150
풀이는 당연히 DP를 써야한다고 생각했다
- 분할정복과 유사하게 분할을 해 나가는 방식을 생각했었다.
- 자연스레 겹치는 구간들이 여러번 생길 것이기 때문에 DP를 생각했다
인접한 것 끼리만 더할 수 있다는 것
- 테스트 하나당 500개의 장이 있을 수 있다.
- 순서도 중요함
- a1 a2 a3 a4 가 있을 때
- a2+a3 → b1
- b1 + a4 (+ b1 ) = b2
- 분할정복 방식을 사용하면서, 백트랙킹을 하는 방법을 사용하면 안 되려나
- 백트랙킹이라는게 결국은 한 번은 끝 까지 갔다가 오는 거임...
- 우리에게 필요한 것은 어떤 것의 sum을 리턴하는 것 , 그리고 component1 과 component2
- 그니까 백트랙킹은 아니라는 거임
챕터가 2개인 경우
→ 두개를 그냥 더하면 된다.
챕터가 3개 이상인 경우
a1 a2 a3
a1+ a2 = b1
b1 + a3
여기서 합은 → a1 + a2 + b1 + a3임
어떤 함수에서는
전체 구간을 → section1 , section 2 나눈다. → section1 + section2 를 리턴한다.
- left, right가 존재할 때, 구간을 나누는 pivot이 있다고 해 보자
- pivot은 left ≤ pivot < right 로 하고
- 구간은 : [ left ~ pivot ] , [ pivot +1 ~ right ] 로 한다.
- if : right
특정 구간을 분할하는 경우는 여러가지가 있어 보이지만, 결국은 하나의 pivot을 기준으로 left ~ pivot 그리고 pivot +1 ~ right로 나누어진다.
이것을 divide하여 원소가 1일 때 까지 나누다가, 합쳐가는 과정이다
어쨋거나 section의 값이 "최소가 나오게 하면 된다 "
- divide한 section을 계산하는 값이 최소가 나오도록 구해서 , 분할해놓은 tree를 타고 올라가면됨.
import java.io.*;
import java.util.*;
public class Main{
// dp 를 사용해야한다. divide 시켜놓고 합할 건데, 각 부분들은 최소 값을 갖도록 해야한다
// 여기서 최소값은, a + b = A , c + A = B 일 때, a + b + c + A 도 최소.. B도 최소여야하지 않나........
// 근데 a + b + c + A가 최소면 B도 최소네
// 아무튼 recur이 리턴해야하는 것은 무엇일까? 아니, 리턴을 해야하는걸까?
// 현재 상황의, 현재까지 더한 값 : dp[left][i] + dp[i+1][right] + dp[left]
public static int[][][] dp = new int[500][500][2]; // dp[left][right][0] : 여기까지 오는데 필요한값 dp[left][right][1] 은 리턴값
// 현재 주어진 챕터
public static int[] cur = new int[500] ;
// 현재 주어진 챕터의 수
public static int k;
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StringTokenizer st;
public static void setting() throws IOException{
int t = Integer.parseInt(br.readLine());
for(int i =0; i<t; i++){
// 각 테스트
k = Integer.parseInt(br.readLine());
// input
st = new StringTokenizer(br.readLine());
for(int j=0;j<k;j++){
cur[j] = Integer.parseInt(st.nextToken());
}
// dp init
init(k);
recur(0,k-1);
System.out.println(dp[0][k-1][0]);
}
}
/*
* 각 파일의 크기는 양의 정수로 주어지기 때문에 dp 값을 0으로 설정해도 될 듯
* */
public static void init(int k){
for( int r=0; r<k;r++){
for(int c=0;c<k;c++){
dp[r][c][0] =0;
dp[r][c][1] =0;
}
dp[r][r][1] = cur[r];
}
}
// 현재까지오는데 더한 값 -> 현재 값은 포함하지 않음
// 두 개의 원소로 이루어진 경우에는 -> [0]과 [1]이 같을 수 밖에
// 좌측부분 까지 가는데 필요한 값을 dp[left][right][0] 을 통해 얻을 수 있다
// 좌측에서 리턴되는 값은, 그냥 값...
public static void recur(int left, int right){
// return 값, 여기까지 오는데 필요한 값 을 구분하여 저장하도록 하자
if(dp[left][right][1] != 0){
return;
}
// 만약 right = left + 1 이라면 ?
int minSum = Integer.MAX_VALUE;
int min = Integer.MAX_VALUE;
int curSum = 0,cur=0;
for(int i =left;i<right;i++){
recur(left,i);
recur(i+1,right);
curSum = 0;
curSum += dp[left][i][0];
curSum += dp[i+1][right][0];
curSum += dp[left][i][1];
curSum += dp[i+1][right][1];
if(curSum < minSum ){
minSum = curSum;
min = dp[left][i][1] + dp[i+1][right][1];
}
}
dp[left][right][0] = minSum;
dp[left][right][1] = min;
}
public static void main(String[] args) throws IOException{
setting();
}
}
위의 풀이에서 굳이 dp를 3차원으로 할 필요가없었다.
import java.io.*;
import java.util.*;
public class Main{
// dp 를 사용해야한다. divide 시켜놓고 합할 건데, 각 부분들은 최소 값을 갖도록 해야한다
// 여기서 최소값은, a + b = A , c + A = B 일 때, a + b + c + A 도 최소.. B도 최소여야하지 않나........
// 근데 a + b + c + A가 최소면 B도 최소네
// 아무튼 recur이 리턴해야하는 것은 무엇일까? 아니, 리턴을 해야하는걸까?
// 현재 상황의, 현재까지 더한 값 : dp[left][i] + dp[i+1][right] + dp[left]
public static int[][] dp = new int[500][500]; // dp[left][right][0] : 여기까지 오는데 필요한값 dp[left][right][1] 은 리턴값
// 현재 주어진 챕터
public static int[] cur = new int[500] ;
// 현재 주어진 챕터의 수
public static int k;
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StringTokenizer st;
public static void setting() throws IOException{
int t = Integer.parseInt(br.readLine());
for(int i =0; i<t; i++){
// 각 테스트
k = Integer.parseInt(br.readLine());
// input
st = new StringTokenizer(br.readLine());
for(int j=0;j<k;j++){
cur[j] = Integer.parseInt(st.nextToken());
}
// dp init
init(k);
System.out.println(recur(0,k-1));
}
}
/*
* 각 파일의 크기는 양의 정수로 주어지기 때문에 dp 값을 0으로 설정해도 될 듯
* */
public static void init(int k){
for(int r=0;r<k;r++){
for(int c=0;c<k;c++){
dp[r][c] = -1;
}
dp[r][r] = 0;
}
for(int r=0;r<k-1;r++) dp[r][r+1] = cur[r]+cur[r+1];
}
// 현재까지오는데 더한 값 -> 현재 값은 포함하지 않음
// 두 개의 원소로 이루어진 경우에는 -> [0]과 [1]이 같을 수 밖에
// 좌측부분 까지 가는데 필요한 값을 dp[left][right][0] 을 통해 얻을 수 있다
public static int recur(int left, int right){
// return 값, 여기까지 오는데 필요한 값 을 구분하여 저장하도록 하자
if( dp[left][right] != -1){
return dp[left][right];
}
int sum = Integer.MAX_VALUE ;
for(int i= left;i<right;i++){
sum = Math.min(sum,recur(left,i)+recur(i+1,right));
}
// left ~ right 파일 합
for(int i = left; i <= right ; i++){
sum += cur[i];
}
dp[left][right] = sum;
return sum;
}
public static void main(String[] args) throws IOException{
setting();
}
}