fastrail08's blog

By fastrail08, history, 5 months ago, In English

Hi Codeforces! This is my first blog here, so please excuse any rookie formatting or style issues. I’ve been learning Dynamic Programming recently, and I’m currently trying to deepen my understanding of top-down → bottom-up conversion — not just for this problem, but conceptually. I am assuming that my top down code is conceptually sound and correctly implemented, but I am getting WA for the iterative / bottom-up version

Problem

The problem is Maximum Tip Calculator (GFG)

You’re given n orders and two delivery boys — A and B. Each order i gives:

arr[i] → tip if A takes it

brr[i] → tip if B takes it

A can take at most x orders, B can take at most y. We want to maximize the total tip.

I already solved it using the greedy approach (sorting by |arr[i] — brr[i]|), but I wanted to approach it using Dynamic Programming.

My Memoized (Top-Down) Solution:

long long getMaxTip(int idx, int aCount, int x, int y, vector<int> &arr, vector<int> &brr, vector<vector<long long> > &memo){
        // No item left to process, means no profit
        if(idx >= arr.size()){
            return 0;
        }
        
        if(memo[idx][aCount] != -1){
            return memo[idx][aCount];
        }
        
        long long aProfit = 0, bProfit = 0;
        
        //A takes the order
        if(aCount < x){
            aProfit = 1LL * arr[idx] + getMaxTip(idx + 1, aCount + 1, x, y, arr, brr, memo);
        }
        
        //B takes the order
        if(idx - aCount < y){
            bProfit = 1LL * brr[idx] + getMaxTip(idx + 1, aCount, x, y, arr, brr, memo);
        }
        return memo[idx][aCount] = max(aProfit, bProfit);
    }

//State meaning:

//memo[idx][aCount] = max total profit achievable after processing 'idx' total orders and A has already taken aCount orders. (Though it stores the answer for the lower half of the tree (idx -> n - 1))

Bottom-Up Conversion

Now, I tried converting this to an iterative version , O(N^x) space and O(N*x) time— keeping the exact same state definition and recurrence structure.

Reasoning for the code:

In memoized recursion, subproblems come from deeper calls (idx onwards).

So in bottom-up, we should fill from idx = n-1 → 0.

Each state {idx, aCount} depends on {idx + 1, aCount} and {idx + 1, aCount + 1}.

long long getMaxTipDP2D(int x, int y, vector<int> &arr, vector<int> &brr){
      int n = arr.size();
      // state {idx, aCount} = max profit made if we have processed (n - idx - 1) items and A has already taken aCount orders;
      vector<vector<long long> > dp(n + 1, vector<long long>(x + 1, -1));
      
      //base case(from memo code, when idx == n, the profit = 0)
      for(int j = 0; j <= x; j++){
          dp[n][j] = 0;
      }
      
      for(int idx = n - 1; idx >= 0; idx--){
          int totalItemsProcessed = n - idx - 1;
          for(int aCount = 0; aCount <= min(x, totalItemsProcessed); aCount++){
              //current state to fill => {idx, aCount}
              
              long long aProfit = 0, bProfit = 0;
              // A can take order iff range[0, min(totalItems,x))
              // B can take order iff range[0, y)
              //Boundary case check
              if(aCount < x){
                  aProfit = 1LL * arr[idx] + dp[idx + 1][aCount + 1];
              }
              
              
              //Total Items proccessed till now - orders A have taken already
              if(totalItemsProcessed - aCount < y){
                  bProfit = 1LL * brr[idx] + dp[idx + 1][aCount]; 
              }
              
              //Answer of current state
              dp[idx][aCount] = max(aProfit, bProfit);
          }
      }
      long long res = 0;
      for(int j = 0; j <= x; j++){
          res = max(res, dp[0][j]);
      }
      return res;
  }

//State meaning (bottom-up):

//dp[idx][aCount] = max profit made from processing (n - idx - 1) items if A has already taken aCount orders.


❓ My Question

Now, here’s where I’m confused conceptually.

Even if this version might not pass large inputs, I’m curious conceptually:

If I directly mirror the recurrence and keep the same DP state meaning (dp[idx][aCount] = answer of subproblem starting at idx), Is there something fundamentally wrong with this kind of conversion? Or should the state meaning itself change when moving to an iterative representation?

Are my top-down and bottom-up definitions actually identical?

In the memoized version, the subproblem is defined as:

“max profit after processing idx items and A has taken aCount orders” but it stores the answer for the later half (from idx → n-1).

In the iterative version, the subproblem is defined as:

“max profit after processing (n — idx — 1) items and A has taken aCount orders,” and it also stores the answer for the later half.

So, my definitions are slightly different in wording — one counts how many items have been processed so far, the other counts how many remain — yet they both seem to represent the same portion of the problem space / recursive tree logically.

In other words, the definition is different, but the meaning feels the same.

Am I right in thinking that both are just two perspectives of the same state? Or does this verbal mismatch actually affect the correctness or iteration order of the DP?

What I’m Hoping to Learn

I’d really appreciate if someone could explain:

Whether my iterative logic is conceptually consistent with the recursive recurrence, and

If not, what conceptual correction (in meaning, iteration order, or indexing) is needed to make it work cleanly when going bottom-up.

I’m more interested in the transition reasoning & sub problems (definition & actual meaning) than just the final optimized code (though that’s welcome too! :)).

  • Vote: I like it
  • -10
  • Vote: I do not like it

»
5 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Auto comment: topic has been updated by fastrail08 (previous revision, new revision, compare).

»
5 months ago, hide # |
 
Vote: I like it -9 Vote: I do not like it

If you want to become master in DP then prefer this series. Contains 12 different DP Patterns (1D To Graph DP). 160+ DP problems in which 115+ are from LeetCode. Which means by the end of this series you'll end up solving 115+ DP problems of LeetCode. Its for free on LeetCode: https://leetcode.com/discuss/interview-question/5659029/ultimate-dynamic-programming-series-on-leetcode

  • »
    »
    5 months ago, hide # ^ |
     
    Vote: I like it 0 Vote: I do not like it

    Thank you so much for listing a good source of well commented editorials. I skimmed over some editorials of questions that I have already solved :D and found the explanations easy to understand. I will definitely use it as a guide to solve a pattern specific question.

    Although, if you don't mind me asking, could you provide any hints or explanation, on why my bottom up code seems to be giving WA. Thanks for the help!

    • »
      »
      »
      5 months ago, hide # ^ |
       
      Vote: I like it 0 Vote: I do not like it

      Provide your whole Top Down code (means dp array with size initialization, first call made to getMaxTip() with paramteres, etc). Provide this and I'll give you your bottom-up.

      • »
        »
        »
        »
        5 months ago, hide # ^ |
         
        Vote: I like it 0 Vote: I do not like it

        long long getMaxTip(int idx, int aCount, int x, int y, vector<int> &arr, vector<int> &brr, vector<vector<long long> > &memo){ // No item, means no profit if(idx >= arr.size()){ return 0; } if(memo[idx][aCount] != -1){ return memo[idx][aCount]; } long long aProfit = 0, bProfit = 0; //A takes the order if(aCount < x){ aProfit = 1LL * arr[idx] + getMaxTip(idx + 1, aCount + 1, x, y, arr, brr, memo); } //B takes the order if(idx - aCount < y){ bProfit = 1LL * brr[idx] + getMaxTip(idx + 1, aCount, x, y, arr, brr, memo); } return memo[idx][aCount] = max(aProfit, bProfit); } long long maxTip(int n, int x, int y, vector<int> &arr, vector<int> &brr) { // code here vector<vector<long long> > memo(n, vector<long long>(x + 1, -1)); return getMaxTip(0, 0, x, y, arr, brr, memo); }
        • »
          »
          »
          »
          »
          5 months ago, hide # ^ |
          Rev. 2  
          Vote: I like it 0 Vote: I do not like it
          class BottomUp {
              using LL = long long;
          
              // O(N*X) & O(N*X)
              LL solveWith2DTable(int n, int x, int y, vector<int>& arr, vector<int>& brr) {
                  vector<vector<LL>> dp(n + 1, vector<LL>(x + 1, -1));
          
                  for(int aCount = 0; aCount <= x; ++aCount) // Init base case (i == n)
                      dp[n][aCount] = 0;
          
                  for(int i = n - 1; i >= 0; --i) {
                      for(int aCount = x; aCount >= 0; --aCount) {
                          long long aProfit = 0, bProfit = 0;
          
                          if(aCount < x) {
                              aProfit = 1LL * arr[i] + dp[i + 1][aCount + 1];
                          }
                          if(i - aCount < y) {
                              bProfit = 1LL * brr[i] + dp[i + 1][aCount];
                          }
          
                          dp[i][aCount] = max(aProfit, bProfit);
                      }
                  }
          
                  return dp[0][0];
              }
          
              // O(N*X) & O(N*X)
              LL solveWith2DEnhanced(int n, int x, int y, vector<int>& arr, vector<int>& brr) {
                  vector<vector<LL>> dp(n + 1, vector<LL>(x + 1, 0));
          
                  for(int i = n - 1; i >= 0; --i) {
                      for(int aCount = x; aCount >= 0; --aCount) {
                          long long aProfit = 0, bProfit = 0;
          
                          if(aCount < x) {
                              aProfit = 1LL * arr[i] + dp[i + 1][aCount + 1];
                          }
                          if(i - aCount < y) {
                              bProfit = 1LL * brr[i] + dp[i + 1][aCount];
                          }
          
                          dp[i][aCount] = max(aProfit, bProfit);
                      }
                  }
          
                  return dp[0][0];
              }
          
              // O(N*X) & O(2*X)
              LL solveWith1DTable(int n, int x, int y, vector<int>& arr, vector<int>& brr) {
                  vector<int> nextRow(x + 1, 0);
          
                  for(int i = n - 1; i >= 0; --i) {
                      vector<int> idealRow(x + 1, 0);
          
                      for(int aCount = x; aCount >= 0; --aCount) {
                          long long aProfit = 0, bProfit = 0;
          
                          if(aCount < x) {
                              aProfit = 1LL * arr[i] + nextRow[aCount + 1];
                          }
                          if(i - aCount < y) {
                              bProfit = 1LL * brr[i] + nextRow[aCount];
                          }
          
                          idealRow[aCount] = max(aProfit, bProfit);
                      }
          
                      swap(nextRow, idealRow);
                  }
          
                  return nextRow[0];
              }
          
          public:
              LL maxTip(int n, int x, int y, vector<int>& arr, vector<int>& brr) {
                  return solveWith1DTable(n, x, y, arr, brr);
              }
          };
          // With Love by vHiren
          
          • »
            »
            »
            »
            »
            »
            5 months ago, hide # ^ |
            Rev. 2  
            Vote: I like it 0 Vote: I do not like it

            Thanks for the code :D. Could you please tell 2 things:

            1) meaning of DP[i][aCount] in your code?

            2) could you explain the part where you did: if(i - aCount < y){}

            'i' here should represent the totalItems already processed and the whole expression should check if B could take anymore orders

            But as we are moving from (n -> 0), does i not represent the items that are yet to be processed and (n - i - 1) are already proccessed.

            totalItemsAlreadyProccessed - #orders by A = #orders by B

            i.e. the 'if' should be:

            if((n - i - 1) - x < y){}