Fastrail08

Longest Divisible Subset(length) O(N^2) time and O(N) space COMMENTS NEED UPDATION

Jul 27th, 2025
141
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 7.55 KB | None | 0 0
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3.  
  4.  
  5. //APPROACH 1 T.C. O(N^2), SPACE - O(N^2)
  6. int getMaxChain(int level, int lastIdx, vector<int> &v, vector<vector<int> > &memo1){
  7.     if(level >= v.size()){
  8.         return 0;
  9.     }
  10.     if(lastIdx != -1 && memo1[level][lastIdx] != -1){
  11.         return memo1[level][lastIdx];
  12.     }
  13.     int lengthInc = 0, lengthExc = 0;
  14.     // include current level only when the lastIdx element divides level or the element being included is the FIRST element of the subset
  15.     if(lastIdx == -1 || v[level] % v[lastIdx] == 0){
  16.         lengthInc = 1 + getMaxChain(level + 1, level, v, memo1);
  17.     }
  18.     //exclude, no condition needed on exclusion, as it doesn't change the subset, so exploring this option won't violate the property of subset being formed
  19.     lengthExc = getMaxChain(level + 1, lastIdx, v, memo1);
  20.     if(lastIdx != -1){
  21.         memo1[level][lastIdx] = max(lengthInc, lengthExc);
  22.     }
  23.     return max(lengthInc, lengthExc);
  24. }
  25.  
  26. //APPROACH 2 T.C. O(N^2) SPACE - O(N^2) [optimal]
  27. //USE BINARY SEARCH (Binary search can't be used as the predicate logic doesn't mean anything when used to disregard a half)
  28. // BINARY SEARCH can only be used in an increasing range. We already SORTED the array to guarantee the maximum length of SUBSET.
  29. //BINARY SEARCH reduces the T.C and S.C by removing redundancy in the MEMO KEY
  30. // previously we had memo(level, lastIdx) which will be reduced to memo(level).... WHY???
  31. //What lastIdx was being tracked? TO DECIDE WHETHER THE CURRENT ELEMENT CAN BE INCLUDED IN THE SET OR NOT(satify property)
  32. // Why this was necessary? TO FORM ONLY A VALID SEQUENCE/SUBSET BY ONLY PICKING THOSE ELEMENTS THAT FOLLOW PROPERTY
  33. //(process EVERY element in the array, use CONDITIONAL to avoid INVALIDS)
  34. // Essentially we move to level + 1, when we explore, that mostly takes us to some INVALID levels, where we need to USE some CONDITIONAL to AVOID EXPLORING OPTIONS that will result in a faulty subset.
  35. // because we are using INDEX BASED TRAVERSAL, and we EXPLORE OPTIONS at EACH LEVEL(level) and MOVE TO NEXT LEVEL(level + 1), NO MATTER which OPTION we explore. (we are doing BLIND RECURSION(incrementing level by 1) with a CONDITIONAL to guarantee generation of VALID SUBSETS. GOOD APPROACH but we can do better.
  36.  
  37. // WHAT BINARY SEARCH HELPS US WITH => *********IMPORTANT
  38. // RECURSE SMARTLY, for EVERY OPTION EXPLORED, JUMP DIRECTLY TO THE NEXT VALID INDEX, SKIP THE IN BETWEEN INVALIDS.
  39.  
  40. //We won't do BLIND RECURISON anymore, We won't INCREMENT LEVEL BY 1 for EVERY OPTION that we EXPLORE at any level
  41. // We will do SMART RECURSIION. INCREMENT level by SOMETHING, that DIRECTLY takes us to NEXT VALID INDEX.
  42. // WHAT THIS WILL DO? -> from some VALID LEVEL we are on, JUMP TO SOME LEVEL DIRECTLY, that is the NEXT VALID.
  43. // What we were doing earlier?
  44. // from some LEVEL, EXPLORE VALID OPTIONS(use conditionals), JUMP to the NEXT LEVEL that might be INVALID/VALID(because we do blind increment by 1 to generate all possible combination and choose the BEST)
  45.  
  46.  
  47. int getMaxChainWithJUMPS(int level, vector<int> &v, vector<int> &memo2){
  48.     if(level >= v.size()){
  49.         return 0;
  50.     }
  51.     if(memo2[level] != -1){
  52.         return memo2[level];
  53.     }
  54.     //****IMPORTANT size of subset would atleast be 1(that element itself/ a1 itself, property self satisfied)
  55.     //don't return 0, as the element itself is valid subset following the property
  56.     int length = 0;
  57.     bool flag = false;
  58.    
  59.     //include only if it doesn't violate the property of subset
  60.     //explore options then JUMP STRAIGHT to the NEXT VALID level
  61.     //NEXT VALID ELEMENT would be any element in range[level + 1, v.size() - 1] that follows v[i] % v[level], i > level
  62.    for(int i = level + 1; i < v.size(); i++){
  63.        if(v[i] % v[level] == 0){
  64.            flag = true;
  65.            length = max(length, 1 + getMaxChainWithJUMPS(i, v, memo2));
  66.        }
  67.        
  68.    }
  69.    
  70.    return memo2[level] = flag == true ? length : length + 1;
  71. }
  72.  
  73. int main() {
  74.     // your code goes here
  75.     int n;
  76.     cin >> n;
  77.     vector<int> v(n);
  78.     for(int i = 0; i < n; i++){
  79.         cin >> v[i];
  80.     }
  81.     // IMPORTANT We need to find the chain of maximum length using a SUBSET of elements and not SUBSEQUENCE
  82.    
  83.     //SUBSEQUENCE is where ORDER matters, and so you can't change the original sequence of elements, eg LIS, so no sorting
  84.     //SUBSET is where you pick elements no matter the ORDER. SUBSET is basically some COMBINATION of elements NOT necessarily CHOSEN/PICKED in ORDER as they appear in the original array(subset sum question, mask).
  85.    
  86.     // So you can pick element in any order, put them together in a sequence where it will maximise the length.
  87.     // the property {a1|a2|a3} where a1 divides a2, and a2 divides a3
  88.     //AN IMPORTANT OBSERVATION - to suffice the above rule, a1 < a2 < a3
  89.    
  90.     // So to get the maximum length we need to make sure that elements are in ascending order, GUARANTEEING the chance to find the LONGEST SEQUENCE.
  91.     sort(v.begin(), v.end());
  92.    
  93.     //memo table APPROACH 1 = memo(level, lasIdx) T.C. AND SPACE = O(N^2)
  94.     vector<vector<int> > memo1(n, vector<int>(n, -1));
  95.     // cout << getMaxChain(0, -1, v, memo1) << '\n';
  96.    
  97.     //memo table APPROACH 2 = memo(level) T.C ~ O(N^2) AND SPACE = O(N)
  98.     int length = 0;
  99.     vector<int> memo2(n, -1);
  100.     //loop through all the elements as our subset can start with any element not necessarily the first one.
  101.     //give every element the chance to be the first element in the subset
  102.     //As implicit recursion takes care of only calling/moving to valid levels by using the LOOP, but the initial/original recursive call from main(that I called, was without validation), if called without loop, would always start with 0th index (general index based traversal), and we start recursion with 0th element. IN OUR RECURSIVE DEFINITION, AS WE ONLY EXPLORE VALID LEVELS,SO IF A STATE IS REACHED IN RECURSION, RECURSION ASSUMES THAT IT IS A VALID LEVEL(means the last element included will always divide the current element), AND WE ARE GOING TO INCLUDE IT as we have reached the level.
  103.    
  104.     //******* IMPORTANT
  105.     //SO IF A STATE IS REACHED, WE ARE ALWAYS GOING TO INCLUDE THE ELMENT.
  106.     //WE ALWAYS REACH A VALID LEVEL(where all the options are valid), so here include and exclude both are valid.
  107.     //BUT INCLUDE ALWAYS INCREMENTS THE LENGTH BY 1 and EXCLUDE DOESN'T, SO WE ONLY EXPLORE THE INCLUDE OPTIONS AT EVERY VALID LEVEL WE JUMP, because that is the best option to explore
  108.     //For the 2nd element in subset, or the 3rd element included in subset, we would have reached that level where we are including the 2nd element or 3rd element, only when that level element is divisible by the previous level element. So we would have called to the level where 2nd element is going to be included from the level where 1st element was included by checking prior to making the call, that if the level we are moving to, where we are going to include/find the 2nd element of subset is divisble by 1st element of the subset at current level, i.e.
  109.     /*
  110.     for(i in range(level + 1 : v.size() - 1)){
  111.         if(v[i] % v[level] == 0){
  112.             recurse to level i.
  113.         }
  114.     }
  115.    
  116.     BUT the first element that is going to included in the subset, does not need to be divisible by anything, because it is the first element, and there is no prior element to it. SO once a level is reached, we are bound to include that element. But the recursive call  
  117.     */
  118.     for(int i = 0; i < n; i++){
  119.         length = max(length, getMaxChainWithJUMPS(i, v, memo2));
  120.     }
  121.     cout << length << '\n';
  122. }
  123.  
Advertisement
Add Comment
Please, Sign In to add comment