Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Great question β you're thinking in the right direction! β But let's break it down carefully.
- You asked:
- Can we say that we can memoize the above code with memo key as memo(level, msf)?
- π Quick Recap of Your Code
- Your function:
- int getLIS2(int level, int msf, string psf, vector<int> &v)
- level: current index in the vector
- msf: maximum so far in the current increasing subsequence
- psf: printed string for debugging (not affecting the logic)
- v: the input array
- β What Are We Trying to Compute?
- You're trying to:
- Print all valid increasing subsequences, and
- Return the length of the Longest Increasing Subsequence (LIS)
- π Can We Memoize It?
- Short Answer: Not directly with memo[level][msf], because msf is an integer, and it can be any value β even INT_MIN.
- Letβs understand why this is tricky.
- β οΈ Why memo[level][msf] Is Problematic
- msf can be any integer, including large positive or negative numbers.
- You can't use msf directly as an index/key in a memo table unless you:
- Normalize it to a small range (not possible in general), or
- Use a map or unordered_map instead of an array.
- So this won't work:
- vector<vector<int>> memo(n, vector<int>(INT_MAX)); // unfeasible
- β Correct Memoization Strategy
- The standard approach is to memoize by level and the index of the previous element included, not by value.
- So instead of passing msf, you pass prevIdx (index of the last element included in the subsequence).
- π Updated Function Signature:
- int getLIS(int level, int prevIdx, vector<int>& v, vector<vector<int>>& dp)
- π§ Logic:
- At each level, you either include v[level] if it's greater than v[prevIdx], or skip it.
- You memoize dp[level][prevIdx + 1] because prevIdx == -1 (no element included yet) needs to be stored at index 0.
- β Correct Memoized Code:
- int getLIS(int level, int prevIdx, vector<int>& v, vector<vector<int>>& dp) {
- if (level == v.size()) return 0;
- if (dp[level][prevIdx + 1] != -1) return dp[level][prevIdx + 1];
- int taken = 0;
- if (prevIdx == -1 || v[level] > v[prevIdx]) {
- taken = 1 + getLIS(level + 1, level, v, dp);
- }
- int notTaken = getLIS(level + 1, prevIdx, v, dp);
- return dp[level][prevIdx + 1] = max(taken, notTaken);
- }
- π§ͺ Example main():
- int main() {
- int n;
- cin >> n;
- vector<int> v(n);
- for(int i = 0; i < n; i++){
- cin >> v[i];
- }
- vector<vector<int>> dp(n, vector<int>(n + 1, -1));
- cout << getLIS(0, -1, v, dp) << '\n';
- }
- π§΅ Summary:
- Version Works? Why
- memo(level, msf) β msf can be any value, unbounded, hard to index
- memo(level, prevIdx) β Limited range (0 to n), easy to memoize
- psf for printing β Useful for output/debugging but not memoized
- You said:
- consider the value to be in range -10^4 to 10^4. What then?
- ChatGPT said:
- Excellent β this changes things.
- If you know in advance that msf (maximum so far) will always be in a fixed range, say from -10β΄ to 10β΄, then yes:
- β You can memoize using memo[level][msf], with appropriate offsetting.
- βοΈ Why It Works:
- Total possible msf values: 20001 (from -10000 to 10000, inclusive).
- So you can safely create a 2D vector like this:
- cpp
- Copy
- Edit
- vector<vector<int>> dp(n, vector<int>(20001, -1));
- Youβll also need to offset the msf by 10000 when storing in the memo:
- dp[level][msf + 10000]
- β Revised Code with msf Memoization
- Here's how you can modify your original function to use msf directly, assuming the known range:
- int getLIS2(int level, int msf, string psf, vector<int> &v, vector<vector<int>> &dp){
- if(level >= v.size()){
- cout << psf << '\n';
- return 0;
- }
- if(dp[level][msf + 10000] != -1)
- return dp[level][msf + 10000];
- int sizeOfSubInc = 0, sizeOfSubExc = 0;
- if(v[level] > msf){
- sizeOfSubInc = getLIS2(level + 1, v[level], psf + to_string(v[level]) + " ", v, dp);
- }
- sizeOfSubExc = getLIS2(level + 1, msf, psf, v, dp);
- return dp[level][msf + 10000] = max(sizeOfSubExc, sizeOfSubInc + 1);
- }
- π§ͺ main() Function:
- int main() {
- int n;
- cin >> n;
- vector<int> v(n);
- for(int i = 0; i < n; i++){
- cin >> v[i];
- }
- vector<vector<int>> dp(n + 1, vector<int>(20001, -1));
- cout << getLIS2(0, -10000, "", v, dp) << '\n';
- }
- π Important Notes:
- Parameter Value/Range Why it works
- msf [-10000, 10000] Bounded, so indexable
- dp[level][msf+10000] [0...n][0...20000] Offset needed for negatives
- π§΅ Summary
- Yes, memoizing using (level, msf) is valid if msf is bounded.
- Use offsetting for negative values.
- This approach is slightly less standard than using prevIdx, but totally valid with known bounds.
- You said:
- Can it be also memoised 1d where memo[level] denotes what is the LIS up 0 to level - 1 index
- ChatGPT said:
- This is a great question, and it touches on the difference between top-down recursion with memoization and bottom-up DP approaches.
- Letβs carefully unpack it.
- π Your Idea:
- You're asking:
- Can we memoize in 1D with memo[level], where memo[level] stores the LIS ending just before index level?
- The short answer:
- π‘ Sort of β but only in specific formulations.
- Let me explain the distinction clearly.
- β 1D Memoization β Possible, but Not with Your Current Top-Down Formulation
- In your current recursive function, you track:
- level: current index you're trying to process
- msf: maximum value of the current subsequence
- So the decision to include/exclude v[level] depends on the actual value of msf
- Hence, you need both level and msf to uniquely identify subproblems.
- So in this top-down recursive structure, memo[level] alone is not sufficient to capture all subproblem states, unless msf is encoded somehow.
- β When 1D DP Works: Bottom-Up Dynamic Programming
- This is the classical LIS in O(nΒ²) using 1D DP:
- int LIS(vector<int>& v) {
- int n = v.size();
- vector<int> dp(n, 1); // dp[i] = LIS ending at index i
- for(int i = 1; i < n; ++i) {
- for(int j = 0; j < i; ++j) {
- if(v[i] > v[j]) {
- dp[i] = max(dp[i], dp[j] + 1);
- }
- }
- }
- return *max_element(dp.begin(), dp.end());
- }
- This uses dp[i] as:
- Length of the Longest Increasing Subsequence ending at index i
- This works because the state space is fully described by just the current index β no need to carry msf or prevIdx.
- π§ Why Top-Down Needs 2D, Bottom-Up Can Use 1D
- Approach State Needed DP Table Dimensions
- Top-Down (Recursive) level, msf or prevIdx 2D
- Bottom-Up (Iterative) Just i and inner loop on j 1D
- β In Summary:
- β Top-down recursion cannot use memo[level] alone unless you're sure msf is fixed or encoded.
- β Bottom-up DP formulation of LIS does use a 1D dp[] array.
- β If you switch to the iterative version, then yes β you only need dp[level].
- You said:
- show me an example where memo[level] will not identify an unique sub problem during memoisation
- ChatGPT said:
- Excellent β let's demonstrate this with an example that shows **why memo[level] alone is not sufficient in a top-down recursive approach to LIS.
- π― Objective:
- Weβll show a case where two different recursive calls reach the same level, but with different msf values, which means memo[level] would overwrite or misrepresent subproblems.
- π’ Example Input:
- vector<int> v = {3, 2, 4};
- Letβs trace your original function:
- int getLIS2(int level, int msf, string psf, vector<int> &v)
- π² Call Tree (Partial)
- Initial Call:
- getLIS2(0, -β, "")
- β level = 0, msf = -β, can include 3 β call:
- getLIS2(1, 3, "3 ")
- β at level = 1, msf = 3
- - v[1] = 2 < 3 β can't include β call:
- getLIS2(2, 3, "3 ")
- β from level = 0, try excluding 3 β call:
- getLIS2(1, -β, "")
- β at level = 1, msf = -β
- - v[1] = 2 > -β β can include β call:
- getLIS2(2, 2, "2 ")
- π Key Observation:
- Look at these two recursive calls:
- getLIS2(2, 3, "3 ")
- getLIS2(2, 2, "2 ")
- They both have level = 2, but:
- Call msf psf
- A 3 "3 "
- B 2 "2 "
- They represent two different states of the recursion β with different constraints:
- In A, you can include v[2] = 4 because 4 > 3
- In B, you can also include 4 because 4 > 2
- But what if they had other values (say v = {3, 5, 4})?
- Then:
- A: msf = 5, v[2] = 4 β can't include 4
- B: msf = 2, v[2] = 4 β can include 4
- These would result in different valid subsequences and different LIS lengths.
- β Why memo[level] Fails:
- If you memoize as memo[level], then both of these states (level = 2) would share the same memo cell, even though:
- Their previous choices were different
- The subsequences being formed are different
- The result of including or excluding v[2] depends on the value of msf, not just the level
- β Solution:
- Use memo[level][msfIndex] or memo[level][prevIdx] to capture both the position and the current state of the subsequence.
- π§΅ Summary
- π memo[level] assumes the result at that index is always the same, regardless of prior context.
- β But LIS depends on whether or not current value can be appended to an increasing subsequence, so you must track that context (msf or prevIdx).
- You said:
- How would my memo[level][msf] have gone out of bound? Initially I have passed msf as INT_MIN, In recursion it won't have given any issue as I am not adding anything, just taking max at every level.
- It would have given error in allocation of msf while initializing the memo table as can't allocate the array beyond 10^9 . Right? Could I have prevented it by using unordered_map<int, long long>?
- ChatGPT said:
- Absolutely! You're spot on with your diagnosis and reasoning β let's break it down and confirm everything clearly.
- β What You Got Right:
- 1. Why memo[level][msf] Goes Out of Bounds
- You're right β msf (maximum so far) can be any value in the integer range, potentially even INT_MIN (β2Β³ΒΉ). So trying to define:
- vector<vector<int>> memo(n, vector<int>(INT_MAX));
- is:
- Not safe: Because msf values are not bounded to a small indexable range
- Not feasible: You'd try to allocate a table with 2^31 columns, which is > 2 billion entries per row, leading to memory allocation errors
- 2. Why It Doesnβt Go Out of Bounds in Recursive Calls
- Again, correct:
- Youβre not incrementing msf or modifying it in any unpredictable way β you only update it with v[level] if included.
- So msf always comes from the input array or stays INT_MIN.
- But the problem is not in runtime logic β it's in memory allocation.
- β Can You Fix It with unordered_map?
- Yes, and thatβs exactly how you should handle unbounded or sparse states.
- Instead of:
- vector<vector<int>> dp(n, vector<int>(somethingHuge));
- You do:
- unordered_map<long long, int> dp;
- Where your key is encoded like:
- long long key = (long long)level * OFFSET + msf;
- or use a pair:
- unordered_map<pair<int, int>, int, pair_hash>
- With a custom hash function if needed (for pair<int,int> as key):
- struct pair_hash {
- template<typename T1, typename T2>
- size_t operator()(const pair<T1, T2>& p) const {
- return hash<T1>()(p.first) ^ (hash<T2>()(p.second) << 1);
- }
- };
- π§΅ Summary
- Statement Verdict
- msf in recursive call won't go out of bound β Correct
- Allocating memo[level][msf] with full int range is unsafe β Correct
- Using unordered_map<int, long long> prevents this β Correct
- Can you memoize msf in map even with large input values? β Yes β safely, because only valid paths are stored
- You said:
- how does this work?
- unordered_map<pair<int, int>, int, pair_hash>
- pair<int, int> for (level, msf) and the other int for storing the LIS ?
- ChatGPT said:
- Great, you're diving into a more elegant and readable way of handling memoization with composite keys using:
- cpp
- Copy
- Edit
- unordered_map<pair<int, int>, int, pair_hash>
- Letβs break this down step-by-step:
- β What This Declaration Means
- unordered_map<pair<int, int>, int, pair_hash> dp;
- This creates a hash map (dp) that:
- Uses a pair<int, int> as the key β specifically, (level, msf)
- Stores an int as the value β in your case, the LIS length from that subproblem
- Uses a custom hash function (pair_hash) to correctly hash the pair<int, int>
- π Why Use This Over long long Encoding?
- Cleaner and more readable
- Avoids manual encoding and offset logic
- Lets the compiler and hash function take care of collisions and uniqueness
- β How to Define pair_hash
- The STL doesn't define a hash function for pair by default, so you provide one:
- struct pair_hash {
- template <class T1, class T2>
- size_t operator()(const pair<T1, T2>& p) const {
- return hash<T1>()(p.first) ^ (hash<T2>()(p.second) << 1);
- }
- };
- This:
- Hashes p.first and p.second independently
- Combines them using XOR and shifting (to reduce collision)
- You said:
- Could I have done my some changes on my memo approach to reduce the caching to 1 D array.
- ChatGPT said:
- Fantastic question β you're asking:
- Could I make some changes to reduce my memoization approach (which uses (level, msf)) from a 2D cache to 1D?
- β Short Answer:
- Yes β but only under a different interpretation of the problem, specifically if you switch to:
- βWhatβs the LIS ending at each index i?β
- In that form, yes β you can use 1D caching, and it's the classical bottom-up dynamic programming approach. But within your current top-down recursive structure with (level, msf), you cannot reduce to 1D without losing essential information.
- π Why Your Current Memo Needs 2D
- State in your recursion:
- getLIS(level, msf)
- Here, level tells you where you are, and msf tells you what you've built so far.
- π‘ level alone is not enough to determine a unique subproblem β because the result depends on what the previous maximum was (msf).
- So you need both level and msf β hence, a 2D structure.
- β When Can You Reduce to 1D?
- If you restructure the problem to match the classic LIS DP:
- dp[i] = length of LIS ending at index i
- This is a bottom-up tabulation approach:
- vector<int> dp(n, 1); // Base case: each element alone is length 1
- for (int i = 1; i < n; ++i) {
- for (int j = 0; j < i; ++j) {
- if (arr[i] > arr[j])
- dp[i] = max(dp[i], dp[j] + 1);
- }
- }
- int lis = *max_element(dp.begin(), dp.end());
- β This Works with 1D Because:
- All information needed is encoded in i and previous dp[j].
- You don't need to track the actual previous value (msf), only compare arr[i] > arr[j].
- You said:
- I want a 1 D array with O(N) space in memo code?
- ChatGPT said:
- Great β you're now targeting:
- π§ A Top-Down Recursive LIS implementation with only a 1D O(N) memo array, not O(NΒ²) or using 2D or map.
- Thatβs absolutely possible, if you slightly reframe your problem.
- β What You Want:
- Recursive LIS
- Top-down (with memoization)
- Only 1D dp[n] space
- So: dp[i] = LIS starting from index i (or ending at i)
- π§ Strategy: Memoize LIS starting from index i
- This shifts the problem slightly:
- int lisFrom(int i, vector<int>& v, vector<int>& dp) {
- if (dp[i] != -1) return dp[i];
- int maxLen = 1; // At least the element itself
- for (int j = i + 1; j < v.size(); ++j) {
- if (v[j] > v[i]) {
- maxLen = max(maxLen, 1 + lisFrom(j, v, dp));
- }
- }
- return dp[i] = maxLen;
- }
- Then call it for all positions in main():
- π§ Explanation:
- lisFrom(i) means: "What's the LIS starting at index i?"
- Only dp[i] is needed to memoize it β thatβs 1D O(N) space
- Total time is still O(NΒ²) due to nested recursion
- π Optional Improvement (Path Recovery)
- If you want to print the actual LIS path too, we can maintain a next[i] array alongside.
- Let me know if you'd like that enhancement!
- β Summary:
- Goal Achieved? Method
- Top-down(recursive LIS) β Done
- Memoized β With dp[i]
- 1D space, O(N) β Uses dp[n] only
- You said:
- The O(N^2) time and O(N) space top down DP seemed less intuitive than my memo DP with O(N^2) space
- ChatGPT said:
- Totally understandable! The top-down O(NΒ²) DP with O(N) space approach (the dp[i] = length of LIS starting at i) can feel a bit less intuitive at first compared to the classic memo DP that tracks (level, msf) with a 2D structure. Here's why, and some tips on how to think about them:
- Your original memo DP with (level, msf):
- Explicitly tracks the maximum element so far (msf), i.e., the last included element value.
- Very direct and clear: you decide for each element if you can include it based on msf.
- But this requires caching based on the value of msf, which is tricky (huge range β 2D map or complex structures).
- So, harder to memoize efficiently.
- The 1D top-down DP (dp[i]):
- Tracks LIS length starting at index i (or equivalently, LIS ending at index i in the bottom-up).
- No need to store or compare values explicitly, because you just check if next elements are bigger.
- Much easier to memoize: a simple 1D array where dp[i] caches the result.
- But conceptually a bit more indirect β you think in terms of subproblems starting at i, not maintaining an explicit max-so-far value.
- How to get more intuition for the 1D DP?
- Try this mindset:
- βWhat is the longest increasing subsequence starting from position i?β
- If you can find that for all i, the answer is just the max of those results.
- You check all j > i, and if v[j] > v[i], you can move from i to j.
- You don't carry around msf, because i itself guarantees the current "max" (since v[i] is the last included element at that subproblem).
- Visualization:
- v = [10, 9, 2, 5, 3, 7]
- dp[i] = length of LIS starting at i
- - dp[5] = 1 (only 7)
- - dp[4] = 1 + max(dp[j] where v[j] > v[4]) = 1 + max(dp[5]) = 2
- - dp[3] = 1 + max(dp[4], dp[5]) = 1 + max(2,1) = 3
- - dp[2] = 1 + max(dp[3], dp[4], dp[5]) where v[j] > 2 = 1 + max(3,2,1) = 4
- - ...
- Summary
- Approach Intuitiveness Memo Size
- Memo (level, msf) (value) High Large (maps)
- Memo dp[i] (start from i) Medium O(N)
- If you want, I can help walk through the 1D DP with a step-by-step example or visualization β sometimes seeing it unfold is what makes it click.
- Want me to do that?
- You said:
- yes
- ChatGPT said:
- Awesome! Letβs walk through the 1D top-down DP approach to LIS with a concrete example and visualization. This will help you see why it works and how memoization fits naturally.
- Step-by-step: LIS with 1D DP dp[i] = length of LIS starting at i
- Input:
- v = [10, 9, 2, 5, 3, 7]
- Goal:
- For each index i, find the length of the longest increasing subsequence starting at i.
- 1οΈβ£ Base idea:
- If you start at i, you must include v[i].
- Then look forward for any j > i where v[j] > v[i] β you can extend the sequence by going to j.
- Among all such js, pick the one that leads to the longest subsequence.
- If no such j exists, the LIS starting at i is just length 1 (the element itself).
- 2οΈβ£ Initialization:
- vector<int> dp(n, -1);
- dp[i] = -1 means "not calculated yet."
- When computed, dp[i] will store length of LIS starting at i.
- 3οΈβ£ Recursive formula:
- int lisFrom(int i) {
- if (dp[i] != -1) return dp[i];
- int maxLength = 1; // minimum LIS length starting at i (the element itself)
- for (int j = i + 1; j < n; j++) {
- if (v[j] > v[i]) {
- maxLength = max(maxLength, 1 + lisFrom(j));
- }
- }
- return dp[i] = maxLength;
- }
- 4οΈβ£ Walkthrough with example:
- Index Value Calculations dp[i] result
- 5 7 No next elements >7 1
- 4 3 Next bigger: 7 at index 5 β 1 + dp[5] = 2 2
- 3 5 Next bigger: 7 at index 5 β 1 + dp[5] = 2 2
- 2 2 Next bigger: 5 at 3 (dp=2), 3 at 4 (dp=2), 7 at 5 (dp=1) β max is 1+2=3 3
- 1 9 Next bigger: 10 at 0 is no (index 0 < 1), no next bigger β 1 1
- 0 10 No bigger next β 1 1
- But wait β index 1βs value is 9 and 0βs value is 10, so next bigger after 9? Actually, no next bigger after 9 because 10 is before 9.
- 5οΈβ£ Final answer:
- The LIS length is max of all dp[i]:
- max(dp) = max(1, 1, 3, 2, 2, 1) = 3
- The LIS length is 3 (which is [2, 3, 7] or [2, 5, 7]).
- 6οΈβ£ Visualization as recursion tree (partial):
- lisFrom(0) = 1
- lisFrom(1) = 1
- lisFrom(2) = 1 + max(lisFrom(3), lisFrom(4), lisFrom(5))
- = 1 + max(2, 2, 1)
- = 3
- lisFrom(3) = 1 + lisFrom(5) = 1 + 1 = 2
- lisFrom(4) = 1 + lisFrom(5) = 1 + 1 = 2
- lisFrom(5) = 1
- 7οΈβ£ Memoization effect:
- Once dp[i] is computed, itβs stored. If lisFrom(i) is called again, it instantly returns the cached result β no repeated recomputation!
- You said:
- how can map.reserve(1 << 20) or something similar can be used to speed up the execution
- ChatGPT said:
- Excellent question! When using unordered_map in C++, especially in performance-critical or competitive programming settings, calling .reserve() like:
- unordered_map<KeyType, ValueType> map;
- map.reserve(1 << 20);
- can make a noticeable difference in execution time. Here's a breakdown of why it works, when to use it, and how it helps:
- π Why map.reserve() speeds up execution
- π unordered_map uses hash tables internally
- It stores key-value pairs in buckets.
- When the number of elements exceeds the current capacity * load factor, it rehashes β meaning it resizes and re-inserts all elements into a new table.
- Rehashing is expensive (time & memory) and can happen multiple times if many elements are inserted without reserving space.
- π‘ reserve(n) tells the map:
- "I will insert around n elements β allocate bucket space ahead of time."
- βI plan to insert about 1 million entries, so allocate space now to avoid costly rehashing later.β
- This avoids costly rehashing during insertions.
- π§ Example: Speeding up memoized DP
- In your memoized LIS:
- unordered_map<pair<int, int>, int, pair_hash> dp;
- If you expect about 1 million states (e.g., N β 10^5, small number of msf values), you can safely reserve:
- dp.reserve(1 << 20); // β 1 million slots
- This avoids:
- Multiple rehashing operations
- Random memory allocations
- Poor cache locality due to resizing
- Result: ~10β50% speedup is not uncommon in practice.
- β When to use .reserve()
- Situation Should use .reserve()?
- Inserting many elements (> 1000) β
- Memoization with large state spaces β
- Competitive programming β β β
- Small inputs (e.g. < 100 entries) β (wonβt help much)
- β οΈ Notes and Tips
- reserve() is not a hard limit β you can still insert more; it just pre-allocates.
- You can also use .max_load_factor() to control when rehashing happens.
- Example:
- dp.max_load_factor(0.7); // Default is 1.0
- dp.reserve(1 << 20);
- If you're using unordered_map<pair<int,int>,...> often, and speed is really critical:
- Consider switching to gp_hash_table from GNU pbds (even faster).
- Or roll your own struct HashPair with bit-mixing for better hash distribution.
- β In Short:
- Calling:
- map.reserve(1 << 20);
- prevents internal rehashing, keeps performance smooth and predictable, and is a best practice for any non-trivial use of unordered_map.
- You said:
- best hashing for pair as a key
- ChatGPT said:
- Great question β using std::pair as a key in an unordered_map requires a custom hash function, because the standard library does not provide a hash for pair by default.
- Hereβs how to define and use an efficient, reliable, and portable custom hash for pair<int, int>.
- β Best Hashing for pair<int, int> (C++17+ friendly)
- π§ Recommended Approach: struct pair_hash
- struct pair_hash {
- size_t operator()(const pair<int, int>& p) const {
- return hash<int>()(p.first) ^ (hash<int>()(p.second) << 1);
- }
- };
- This works well and is often used in competitive programming and practical systems.
- Example usage:
- unordered_map<pair<int, int>, int, pair_hash> dp;
- dp.reserve(1 << 20); // Reserve space to avoid rehashing
- π§ Why this hash works:
- Uses std::hash<int> for each part.
- ^ (x << 1) mixes the second value, reducing collisions.
- This is based on boost::hash_combine pattern.
- π§ͺ If you want stronger collision resistance (e.g., in adversarial settings):
- Use a hash function like SplitMix64, which is very robust:
- struct custom_hash {
- static uint64_t splitmix64(uint64_t x) {
- x += 0x9e3779b97f4a7c15;
- x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
- x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
- return x ^ (x >> 31);
- }
- size_t operator()(pair<int, int> p) const {
- uint64_t hash1 = splitmix64(p.first);
- uint64_t hash2 = splitmix64(p.second);
- return hash1 ^ (hash2 << 1);
- }
- };
- This is particularly useful in codeforces/online judges to prevent TLEs due to hash collisions in unordered_map.
Add Comment
Please, Sign In to add comment