MCM based DP or Partition DP

Problems that involve repeatedly placing partition between start and end indices

ยท

6 min read

These problems can be formulated as increasing order of lengths between intervals they involve creating some specific set of partitions between a given range

Number of ways to evaluate Boolean expression to true

the question can be understood as number of ways we can place partitions in this complete string such that it evaluates to true

METHOD 1 : RECURSION AND MEMOIZATION

  • We will try to place 'k' partitions b/w range [s,e]

  • Partitions will be done on each `operand`

  • The answer to each range should consists of a pair of values , the number of way this subrange can evaluate to true and number of ways this subrange can evaluate to false. We can use both these numbers to calculate the answer for bigger subrange using the solutions of smaller subranges - OPTIMAL SUBSTRUCTURE

  • Lets start by creating an utility function for above table logic

    • We can start building recursive solution now

Since we have a lot of repeated subproblems , the above will give TLE unless memoized

METHOD 2 : DYNAMIC PROGRAMMING APPROACH

  • Let dp[i][j] represent a pair of numbers , which represent in how many ways can string expression between [i,j] indices can evaluate to true or false

  • We can store both true ways or false ways in same matrix or create separate matrices also , one storing number of true ways and other storing number of ways of marking range eval to false

  • We can setup the recurrence relation now

      typedef long long ll;
      typedef pair<ll, ll> pll;
      constexpr ll MOD = 1e9 + 7;
    
      ll mod(ll x) {
          return (x % MOD + MOD) % MOD;
      }
    
      pll calc(ll ltc, ll lfc, ll rtc, ll rfc, const char& operand) {
          ll totalWaysToCombine = mod(ltc + lfc) * mod(rtc + rfc);
          ll numberOfWaysToMakeTrue, numberOfWaysToMakeFalse;
    
          switch (operand) {
              case '|':
                  numberOfWaysToMakeFalse = mod(lfc * rfc);
                  return { mod(totalWaysToCombine - numberOfWaysToMakeFalse), numberOfWaysToMakeFalse };
              case '&':
                  numberOfWaysToMakeTrue = mod(ltc * rtc);
                  return { numberOfWaysToMakeTrue, mod(totalWaysToCombine - numberOfWaysToMakeTrue) };
              case '^':
                  numberOfWaysToMakeTrue = mod((ltc * rfc) + (lfc * rtc));
                  return { numberOfWaysToMakeTrue, mod(totalWaysToCombine - numberOfWaysToMakeTrue) };
              default:
                  return { 0, 0 };
          }
      }
    
      int evaluateExp(string & exp) {
          size_t n= exp.size();
    
          vector<vector<pll>> dp(n,vector<pll>(n,{0,0}));
          for(int gap=0;gap<n;gap+=2){// gap is always last-first we are solving for [0,n-1] =>gap (n-1)
              for(int s=0;s<=n-gap;s+=2){// (n-1)-x+1=gap
                  int e=s+gap;// e-s=gap
                  if(gap==0){
                      exp[s]=='T' ?
                        dp[s][e].first=mod(dp[s][e].first + 1) :
                        dp[s][e].second=mod(dp[s][e].second + 1);
                    continue;
                  }
                  ll allWaysToMakeTrue=0 ; ll allWaysToMakeFalse=0;
                  for(int k=s+1;k<e;k++){// all valid operand positions
                      pll leftans=dp[s][k-1];
                      pll rightans=dp[k+1][e];
                      pll numberOfWaysToMakeTruenFalse=calc(leftans.first,leftans.second,rightans.first,rightans.second,exp[k]);
    
                      // adding for the entire range
                      allWaysToMakeTrue=mod(allWaysToMakeTrue + numberOfWaysToMakeTruenFalse.first);
                      allWaysToMakeFalse=mod(allWaysToMakeFalse + numberOfWaysToMakeTruenFalse.second);
                  }
    
                  dp[s][e]=make_pair(allWaysToMakeTrue, allWaysToMakeFalse);
              }
    
          }
          return mod(dp[0][n-1].first);
      }
    

Matrix Chain Multiplication

Like earlier problem this problem also involves placing partitions between matrices such that they result in minimum operations

METHOD 1: Recursion and Memoization

We will place partitions one by one, compute operations for each and find minimum operations among them

METHOD 2: Tabulation

If we have solution for each subrange , we can form the solution for parent range from them . This gives us an hint that we can form our solution on basis of increasing length or gap

Rod Cutting Problem

METHOD-1 Recursion and Memoization

  • We can easily see how formulating this question in the basis of length can make our problem easier

  • Base cases : a zero length rod cannot be sold, a single unit rod can only be sold whole

  • The best price of any subsequent length, would be function solutions of all smaller lengths which are recursively similar

METHOD -2 Bottom up , Tabulation

  • The price array maps `length` to rod price

  • We can create a dp array of size n+1 , at each index of this array we will store the maximum price attainable for rod of length i by any means

  • ie dp[i] represents the maximum possible profit attainable for a rod of length i by any means (selling whole/cutting)

  • Base cases : a zero length rod cannot be sold, a single unit rod can only be sold whole

  • The best price of any subsequent length, would be function of pre-cauluated solutions of all smaller lengths

    TODO: This problem can also be done via Unbounded -knapsack concept

Minimum Cost to cut a stick

  • The diagram in the question gives a great way to emulate the problem, we can try choosing any partition first among possible partitions and find cost of each choice

  • METHOD 1: RECURSION AND MEMOIZATION

METHOD 2: BOTTOM UP DP / Tabulation

Burst Balloons

  • For example for [2,3,5] there are 3! ways to burst baloons, each way will lead to a different coins value

METHOD 1- RECURSION AND MEMOIZATION

  • At each balloon we have a choice to make it as the LAST one to burst, this simplifies calculation for recursive solution

METHOD 2- TABULATION

  • Above cells, for example cell[1][4] (3,1,5,6) represents the BEST price bursting these balloons in any order can give , in context of the entire array ie its actually 2,3,1,5,6,4

  • We can fill the gap=0 cells easily (base case)

  • For all subsequent cells we can reuse table and calculate

Palindrome Partitioning II

10:53

Method 1 : Recursion and Memoization

  • Wherever we find a valid palindromic prefix/suffix- we can place a partition at that point that way we know the answer of the left recursive call already and the problem reduces to the right recursive call

  • We can easily check if for a given string [s,e] is a palindrome again using gap method (tabulation)

Method-2 Tabulation/ Bottom Up DP

  • Dp[i] stores the number of cuts required in string [0,i] if we do via finding palindrome suffix

  • When we find a palindromic suffix we know the answer to the right recursive call (ZERO as its a palindromic suffix), and left recursive calls answer is precomputed in the tabulation

Partition Array for Maximum Sum

Method 1: Recursion and Memoization

  • We start with writing a simple recursive solution - handling for a given index , assuming we have the solution for subsequent index, we can then proceed to memorize the calls too

Method 2: Tabulation

  • Let dp[i] be the maximum sum obtained by partitioning the array of size i

  • We will use the dp approach very similar to palindrome partitioning problem, as we know the solution to suffix problem and we have the prefixes already pre-computed

  • We formulate the problem in such a way that its subproblem solution is already stored with us

Like above we have solved with suffix , we can solve with prefix too

More problems to be added soon ....

ย