Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // IMPORTANT SEE THIS DISCUSSION
- // https://chatgpt.com/share/6862b81c-7a7c-8012-983f-5e2aa76d97c0
- #include <bits/stdc++.h>
- using namespace std;
- long long getKPartitions(int level, int partitionsFormed, int &k, int &n, vector<vector<long long> > &memo){
- if(level > n){
- if(partitionsFormed == k){
- return 1;
- }
- return 0;
- }
- //memo check
- if(memo[level][partitionsFormed] != -1){
- return memo[level][partitionsFormed];
- }
- long long newPartition = 0, exisitingPartition = 0;
- // create a new partition if capability to create a new partition
- if(partitionsFormed < k){
- newPartition = getKPartitions(level + 1, partitionsFormed + 1, k, n, memo);
- }
- //don't create a new partition, become part of each exisiting partition one by one
- // for(int i = 0; i < partitionsFormed; i++){
- // exisitingPartition += getKPartitions(level + 1, partitionsFormed, k, n, memo);
- // }
- //You can actually replace the call with the following ------
- exisitingPartition = getKPartitions(level + 1, partitionsFormed, k, n, memo);
- exisitingPartition *= partitionsFormed;
- // WHY??
- // What are we doing here? we are calling the same function exactly equal to the number of partitions present at the current level. If the number is not creating a new partition it can become a part of every partition present at that level, giving a new way to partition each time.
- // Also the loop is doing the same thing -
- /*
- for(0 -> partitionsFormed - 1){
- call Kpartition [partitionsFormed] number of times.
- }
- Basically doing ------
- count = Kpartition() * partitionsFormed
- */
- /*
- Now if you see the recursive function, the params that are changing are (level, k). so memo(level, key).
- Also what does that mean ------
- memo(level/i, k/x) = After processing upto ith index, if number of partitions formed till now is x, any subproblem falling under this category will have the same count of ways to distribute (n - i) elements into (k - x) partitions . What this means is we are memoising number of ways to disribute if the number of elements remaining to be partitioned is (n - i), and the number of partitions left is (k - x).
- SO IT DOESN'T MATTER WHAT THE ACTUAL ELEMENTS ARE IN THE PARTITION, just the number of elements that are already processed and the number of partitions that are already used in processing them.
- Actually the memo defintion contains (i, x), but the value that is memoised is (n - i, k - x)... just imagine what part of tree we are avoiding to recalculate.
- The part that we are avoiding to recalculate is how to distribute (n - i) elements into (k - x) partitions. This is actually what DP is. AVOID RECALCULATION OF SMALLER SUBPROBLEMS.
- WHY??
- Because partition doesn't depend on the ELEMENTS or PARTITIONS itself
- for eg - (n - i) = 5, (k - x) = 2
- ******Number of ways to DISTRIBUTE 5 elements into 2 partitions will always have the same REGARDLESS of the what the value of the elements are.
- THIS IS WHERE IT DIFFERS WITH THE QUESTION - partition n elements into k equal sum partitions.
- Imagine how would you achieve this recursively?
- Keep track of (level, k, partitionSumVector). We need to maintain a partitionSum vector of size k, so that in base case we can actually compare the sum of all k partitions, in whatever case, there are k partitions all with equal sum will contribute to answer.
- Now in the recursive code, 3 params are changing...
- (number of indices processed, number of partitions left to form, sum of every partition formed).
- so MEMO(level, k, partitionSum)
- so memo key = [level + "|" k + "|" + serializeIntoString(partitionSum)]
- Alternatively you can think as -
- WHAT ALL PARAMS ARE NEEDED TO MAKE VALID/INVALID DECISIONS IN THE BASE CASE??
- 1) we need a level > n check to see if all elements are processed
- 2) we need a k == 0 check to see if exactly k partitions are formed
- 3) If both of the above are met, compare the sum of all k partitions and see if they are equal.
- If all 3 condns are met, return 1 else 0
- So we needed these 3 variables to make decision, that's why all 3 are to be memoised.
- so if you try to memoise with key as (level, k) it would insufficient, as the partitionSum vector might have different value.
- And if it differs, the decision making in base case may also differ.
- FOR EG ------ consider array of 7 elements and 4 partitions, now if in the base case we have the params as -
- level = 7(all indices processed), 0 partitions left to form(all 4 partitions formed),sum of each partition
- (7, 0, [8, 8, 8, 8]) & (7, 0, [4, 6, 4, 2])
- But the first case is a VALID ANSWER (all 4 partitions equal) which will return 1 and the second case is INVALID (sum not equal) which will return 0.
- But if you had memoised based on (level, k) both had the same key (7, 0) ...which makes essentially an OVERLAPPING IDENTICAL SUBPROBLEM but they are not as the answers returned from both the cases are different. They are OVERLAPPING NON IDENTICAL SUBPROBLEMS, misclassified...
- When this happens, when you MISCLASSIFY two different subproblems under 1 key,
- YOU ARE MEMOISING WITH AN INCOMPLETE KEY!!!
- OR
- MEMO KEY DOES NOT REPRESENT THE COMPLETE STATE OF THE SUBPROBLEM
- OR
- STATE UNDER SPECIFICATION (USING LESS VARIABLES TO DEFINE THE PROBLEM)
- ******Need more uniqueness in the key to differentiate subproblems correctly
- */
- return memo[level][partitionsFormed] = newPartition + exisitingPartition;
- }
- int main() {
- // your code goes here
- int n, k;
- cin >> n >> k;
- vector<vector<long long> > memo(n + 1, vector<long long>(k + 1, -1));
- cout << getKPartitions(1, 0, k, n, memo) << '\n';
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement