Memoization / Top Down Approach
- dp function must be of backtracking(
dfs
) type and MUST have return type - create recursive function with
states
-- returnType foo(STATES)states
only means variables that changes during recursion
- Write down the Recurrence Relation with base cases (TRANSITIONS)
- add a variable
like ans = -1
for comparison purpose & then always UPDATE it likememo[row][bud] = ans;
, So as to get the Most Optimized Solution - the
memo[][optmized value]
- The
column
of thememo
table MUST be the Optimization Value
- The
β Parts of a Memoization Solutionβ
Gobal variablesβ
- input array/vector
memo[][]
ormemo[]
table to store already donedp(..)
state values
parts of dp(...)
functionβ
- Base case
return
value of the functiondp(..)
likeif (x == 0) return 0;
it determines the
dp(..)
is returning aCount
of steps or min/max` Amount
- declare ans variable
ans = -1e8
- use
ans
variable like for storingmin(ans, ..)
ormax(ans, ..)
ans = min(ans, (dp(x-c) + 1) );
for
loop to traverse the given problem's arrayfor (auto c : coins) { ..
for (int k=1; k<v[row][0]; k++) { ..
- Update the
memo[][]
table withans
got after the for loopmemo[..][..] = ans
return ans
β how to write recursive functions with return
typeβ
memset()
β
β use memset for single int values
int memo[MAX_gm][MAX_M]; // g < 20 and b <= 200
memset(memo, -1, sizeof memo); // TOP-DOWN: init memo
β
assign()
β
redeclaring the whole vector
// assign
memo.assign(MAX_gm, vector<int>(MAX_M, 0));
β
fill()
β
we must the ROW size
// fill
fill(memo.begin(), memo.end(), vector<int>(MAX_M, 1));
β clear() + resize()β
alwasy use
v.assign()
instead of this
// clear + resize
memo.clear();
memo.resize(MAX_gm, vector<int>(MAX_M, -2));
βIdeal memoization DP Solution formatβ
int MAX_price = 210;
int MAX_gar = 30;
int MAX_K = 30;
int M, C;
vector<vector<int>> v(MAX_gar);
vector<vector<int>> memo(MAX_gar); // memo table
int dp(int row, int bud) // only have STATES as parameters othe the DP function
{
if (bud < 0)
return -1e9;
if (row == C)
return M - bud; // bud is remaining budget
if (memo[row][bud] != -1)
return memo[row][bud];
int ans = -1;
for (int k = 1; k <= v.at(row).at(0); k++)
{
ans = max(ans, dp(row + 1, bud - v.at(row).at(k)));
}
memo[row][bud] = ans; // update memo table [only after the for loop ends], so that all the values from EACH row has been taken
return ans; // or 'return memo[row][bud]' BOTH are same right now we made ans the same as memo[row][bud]
}
int main()
{
int t;
cin >> t;
while (t--)
{
fill(v.begin(), v.end(), vector<int>(MAX_K, -1));
fill(memo.begin(), memo.end(), vector<int>(MAX_price, -1));
cin >> M >> C;
for (int row = 0; row < C; row++)
{
cin >> v.at(row).at(0);
for (int i = 1; i <= v.at(row).at(0); i++)
{
cin >> v.at(row).at(i);
}
}
int bud = M, row = 0;
int ans = dp(row, bud);
if (ans >= 0)
cout << ans << endl;
else
cout << "no sol" << endl;
}
return 0;
}
/*
3
9 3
3 6 4 8
2 5 10
4 1 5 3 5
20 3
3 6 4 8
2 5 10
4 1 5 3 5
// no sol
// 19
*/
βΌοΈπ΅ printing optimal choices in DPβ
from CP book 4_iβ
an example by CP_4_1
int MAX_price = 210;
int MAX_gar = 30;
int MAX_K = 30;
int M, C;
vector<vector<int>> v(MAX_gar);
vector<vector<int>> memo(MAX_gar); // table
int dp(int row, int bud)
{
if (bud < 0)
return -1e9;
if (row == C)
return M - bud; // bud is remaining budget
if (memo[row][bud] != -1)
return memo[row][bud];
int ans = -1;
for (int k = 1; k <= v.at(row).at(0); k++)
{
ans = max(ans, dp(row + 1, bud - v.at(row).at(k)));
}
memo[row][bud] = ans;
return ans;
}
void print_dp(int row, int bud) // VOID function
{
if ((row == C) || (bud < 0))
return; // similar base cases
for (int k = 1; k <= v[row][0]; ++k) // which model k is opt?
{
if (dp(row + 1, bud - v[row][k]) == memo[row][bud]) // using memo[][] created above in `dp()` function
{
cout << "- " << v[row][k] << " ";
print_dp(row + 1, bud - v[row][k]); // recurse to this only
break;
}
}
cout << endl;
}
int main()
{
int t;
cin >> t;
while (t--)
{
fill(v.begin(), v.end(), vector<int>(MAX_K, -1));
fill(memo.begin(), memo.end(), vector<int>(MAX_price, -1));
cin >> M >> C;
for (int row = 0; row < C; row++)
{
cin >> v.at(row).at(0);
for (int i = 1; i <= v.at(row).at(0); i++)
{
cin >> v.at(row).at(i);
}
}
int bud = M, row = 0;
int ans = dp(row, bud);
if (ans >= 0)
cout << ans << endl;
else
cout << "no sol" << endl;
print_dp(row, bud);
}
return 0;
}
/*
3
9 3
3 6 4 8
2 5 10
4 1 5 3 5
20 3
3 6 4 8
2 5 10
4 1 5 3 5
// no sol
// - 6
// 19
// - 6 - 10 - 3 β
*/
Other Waysβ
Problems of top-down approach:β
- It occupies stack memory, which is limited
- Stack memory is slow. One of the prime causes are, memory allocation is not performed at once but on demand
- It occupies more memory as function information are stored too
- To very few cases it's easy to think in tabular form
- Tabular form by default uses memoization, thus code is much shorter