알고리즘 스터디에서 친구가 가져왔던 문제이다. 0-1 Knapsack문제 유형을 보고 잡은 문제라는데, 좀 어려웠다.
그냥 0-1 knapsack 문제로 생각하면, 배낭크기를 메모리로 따져서 col에 M개 열을 만들어 해볼까 했는데 메모리의 최대값이 10000000이라 배열으로 선언을 할 수 없을 것 같다.
그럼 어떻게 할까...? 친구가 풀다가 솔루션을 확인했는데, 그 방식이 너무 신기했다. col을 다른 값을 사용해서 채우는 방법이 있더라. 이 문제 같은 경우에는, col에 cost를 넣어둬 문제를 해결했다.
따라서 dp[i][j] = i번째 앱까지 봤을때, j비용으로 얻을 수 있는 메모리의 크기가 된다.
dp 배열의 내용을 한번 보자,
[0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | |
dp[1] | 0 | 0 | 0 | 30 | 30 | 30 | 30 | 30 | 30 |
dp[2] | 10 | 10 | 10 | 40 | 40 | 40 | 40 | 40 | 40 |
dp[3] | 10 | 10 | 10 | 40 | 40 | 40 | 60 | 60 | 60 |
dp[4] | 10 | 10 | 10 | 40 | 40 | 45 | 60 | 60 | 75 |
dp[5] | 10 | 10 | 10 | 40 | 50 | 50 | 60 | 80 | 80 |
배열을 채울 때, 만약 i 번째 앱의 비용이 j 보다 적게 되면(해당 앱을 삭제할 수 없으므로) 그냥 바로 위 값을 가져온다(dp[i][j] = dp[i-1][j])
만약 i번째 앱의 비용이 j와 같거나 크면(해당 앱을 삭제하는 선택을 할 수 있으므로) 바로 위 값과, 그 앱을 삭제했을 때 메모리 양 중 큰 값을 가져온다(std::max(dp[i-1][j], dp[i-1][j-apps[i].cost] + apps[i].mem))
이렇게 순회하며 dp 값을 구하고 나서, 마지막 행에서 우리가 원하는 메모리 양을 넘는 순간의 cost를 구하면, 해당 양의 메모리를 구하는데 필요한 최소 비용을 구할 수 있다.
#include <iostream>
#include <vector>
class app{
public:
int cost;
int memory;
app(int memory)
{
this->memory = memory;
}
};
int main()
{
int N, M;
std::cin >> N >> M;
std::vector<app> apps;
apps.push_back(app(-1));
for (int i = 1; i <= N; i++)
{
int mem_tmp;
std::cin >> mem_tmp;
app temp(mem_tmp);
apps.push_back(temp);
}
int costSum = 0;
for (int i = 1; i <= N; i++)
{
int cost_tmp;
std::cin >> cost_tmp;
apps[i].cost = cost_tmp;
costSum += cost_tmp;
}
// long dp[N+1][costSum+1];
std::vector<std::vector<long> > dp(N + 1, std::vector<long>(costSum + 1, 0));
// memset(dp, 0, sizeof(dp));
for (int i = 1; i <= N; i++)
{
for (int costLimit = 0; costLimit <= costSum; costLimit++)
{
if (costLimit < apps[i].cost) // 현재 앱 죽여도 costLimit 만족 안될때.
dp[i][costLimit] = dp[i - 1][costLimit]; // 그냥 위 칸 당겨오기
else
dp[i][costLimit] = std::max(dp[i - 1][costLimit], dp[i - 1][costLimit - apps[i].cost] + apps[i].memory);
}
}
long mincost;
for (mincost = 0; mincost <= costSum; mincost++)
{
if(dp[N][mincost] >= M)
break;
}
std::cout << mincost;
}
/*
Knapsack 문제와 유사하지만, 어떤 것을 축으로 삼아 제한 조건을 줄 지 선정하는것이 중요하다.
여기서는 dp[i][j] 를, j cost 안에서 i 번째 아이템까지 사용해 확보 가능한 최대 메모리 양으로 정한다.
knapsack 에서는 무게를 하나씩 늘려가며 i번째 아이템까지 사용해 무게 j 배낭에 담을 수 있는 가치였다.
dp를 이렇게 정의해두고, 우리는 어떻게 이 dp를 채워가느냐?
만약, i번째 아이템이 현재 j 보다 비용이 크면, 우리는 이 앱을 삭제할 수 없다. 따라서 그냥 바로 위 (dp[i-1][j])를 가져온다.
만약, i번째 아이템이 현재 j 보다 비용이 작거나 같으면, 우리는 이 앱을 삭제할 수 있다. 삭제 안하는경우(dp[i-1][j])와 삭제 하는 경우(dp[i-1][j - apps[i].cost] + apps[i].memory) 중 최대값을 가져다 dp에 넣는다.
이렇게 완성된 dp 배열은, 마지막 줄을 탐색하며, 우리가 원하는 메모리 양이 넘는 순간의 인덱스(cost)값을 구하면 된다.
바로 그 값이, 그 cost를 최소화 하면서 우리가 원하는 메모리를 확보한 상황이다/
*/
스터디할때 한번 봤었고, 다시 보면서 읽어도 진짜 이걸 문제만 보고 어떻게 솔루션을 내나 싶다. 어떻게 이렇게 문제를 푸는지 신기한 사람들이 많은 것 같다.
요즘 DP를 보는데, 이쪽 영역은 약간 수학의 영역인 느낌이다... 문제를 점화식을 세우는게 가장 중요한데, 그 가장 중요한게 제일 어려운 것 같다.
오늘도 평온한 하루 되길. 슨민.
'짜잘한 기록' 카테고리의 다른 글
프로그래머스 정수 삼각형 (0) | 2021.10.12 |
---|---|
프로그래머스 N으로 표현 (0) | 2021.10.12 |
백준 2096 내려가기 (0) | 2021.10.05 |
백준 11066 파일 합치기 (0) | 2021.10.04 |
백준 1520 내리막 길 (0) | 2021.09.26 |