알고리즘/배낭문제(DP)
BOJ 백준 1231 주식왕 동호
kyjdummy
2025. 4. 11. 21:56
문제 : https://www.acmicpc.net/problem/1231
[ 문제 요약 ]
- 초기 자본이 주어지면, 일자가 증가함에 따라 주식을 사고팔 때, 마지막 날 얻을 수 있는 최대 이익을 구합니다.
[ 테스트 케이스 설명 ]
2 3 10 // 주식수(1<=50), 주어진일자D(2<=10), 초기자금M(1<=이십만)
10 15 15 // 해당 주식이 날짜에 따라 변하는 값이 주어짐, 1<=1000
13 11 20
//24
[ 문제 풀이 ]
이 문제는 그리디 하게 풀어야 합니다.
각 날짜마다 얻을 수 있는 차익의 최대 금액을 구하고, 그 날짜마다 구한 최대 이익을 초기 자금 M에 계속 더해나가고
결과 적으로 M을 출력해 주면 됩니다.
예를 들어 위 테스트 케이스처럼 3일이 주어지면, 이득이 발생하는 일자는 2일부터입니다.
그 이유는 1일 날은 사기만 하고, 팔지는 않기 때문에 결국 추가 이익이 없기 때문입니다.
그래서 반복문을 생각할 때 차익이 발생하는 2일부터 반복문을 시작합니다.
그리고 모든 주식에 대해서 그날 그날마다 얻을 수 있는 최대 이익을 계산합니다. 이런 식으로 풀기 때문에 '그리디' 하다는 것입니다.
이 계산부터 배낭 문제 개념이 나오게 됩니다. 그리고 만약 현재 여유 자금이 10이라면, 주식 가격이 3일 때 한 주식을 3개 살 수 있습니다.
이렇게 다중 선택까지 고려해 반복문을 만듭니다. dp 배열은 아래와 같은 의미를 갖습니다.
dp[현재 자금] = 현재 자금으로 얻을 수 있는 최대 이익
위 설명만 듣고는 어떻게 코드로 구현하는지 이해가 안 갈 수 있어서 코드를 보며 이해를 해야 합니다.
설명은 주석으로 대체합니다.
[ 정답 코드 ]
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
class Main{
public static void main(String[] args)throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken()); // 주식수(1<=50)
int D = Integer.parseInt(st.nextToken()); // 주어진일자(2<=10)
int M = Integer.parseInt(st.nextToken()); // 초기자금(1<=이십만)
int stock[][] = new int[N + 1][D + 1];
for(int i=1; i<=N; i++)
{
st = new StringTokenizer(br.readLine());
for(int j=1; j<=D; j++)
{
stock[i][j] = Integer.parseInt(st.nextToken());// 해당 주식이 날짜에 따라 변하는 값 1<=1000
}
}
// DP를 위한 배열: dp[k]는 'k'라는 자금으로 거래했을 때, 얻을 수 있는 최대 추가 수익을 저장
//문제에서 최종 자금 M이 500,000을 넘지 않는다고 가정하여 크기를 500_001로 잡음.
int dp[] = new int[500_001];
for(int j=2; j<=D; j++) // 일자 반복, 이익이 나는 시점인 2일부터 시작
{
Arrays.fill(dp, 0); // dp값 0으로 초기화
for(int i=1; i<=N; i++) // 주식 종류 반복
{
//'prev'는 어제(j-1일)의 가격, 'profit'은 오늘의 가격과 어제의 가격 차이
// profit이 양수면 이익, 음수면 손실을 의미
int prev = stock[i][j - 1];
int profit = stock[i][j] - prev;
//
//k : 투자 가능한 자금
//반복 범위 : 어제 주식가격 -> 현재 갖고 있는 자금 M까지
//dp[k] = max(dp[k], dp[k - prev] + profit) 의미
//
//현재 k원 자금이 있다면, k원 중 prev원을 사용해 주식을 구입(어제 가격에 구매)하고
//매도하여 profit 만큼 이익을 얻을 수 있다.
//dp[k - prev]는 prev원을 투자하기 전 남은 자금으로 이미 얻은 최대 이익을 의미하므로,
//dp[k - prev] + profit는 현재 prev원을 선택해 얻을 수 있는 이익
//
//이 과정을 모든 가능한 자금 에 대해 반복하면,
//"현재 가지고 있는 전체 자금 M으로 얻을 수 있는 최대 추가 이익" 즉, dp[M]이 계산됨
//
//k가 작은 값부터 M까지 증가하는 이유는, 현재 가능 자금이 10일때, 주식 값이 3원이라면, 3개를 살 수 있는데,
// 이렇게 여러개 사는 것을 표현하기 위해 작은 값부터 증가하며 최대 이익 값을 구함
for(int k=prev; k<=M; k++)
dp[k] = Math.max(dp[k], dp[k - prev] + profit);
}
M += dp[M];
}
// 최종적으로 가질 수 있는 돈의 최댓값 출력, 출력값은 500,000을 넘지않음
System.out.print(M);
}
}