The maximum number achieved by converting two adjacent x to one (x + 1)

For a sequence of N integers, where 1 <= N <= 500 , and the numbers are between 1 and 50 . In step any two adjacent equal numbers xx can be replaced with one x + 1 . What is the maximum number you can achieve with these steps.

For example, if 2 3 1 1 2 2 is specified, then the maximum possible value is 4 :

2 3 1 1 2 2 ---> 2 3 2 2 2 ---> 2 3 3 2 ---> 2 4 2 .

Obviously, I should try to do better than the maximum number available in the sequence. But I can not find a good algorithm.

+5
source share
4 answers

Each input substring can contain no more than one singular (invariant: the logarithmic base of two of the sum of two by the power of each record). For each x we can find many substrings that x can do. For each x this is (1) each occurrence x (2) is the union of two continuous substrings that x - 1 can do. The resulting algorithm is O (N ^ 2) -time.

+2
source

The algorithm can work as follows:

Convert the input to an array where each element has a frequency attribute, discarding duplicate consecutive values ​​in the input to a single node. For example, this input:

 1 2 2 4 3 3 3 3 

It will be presented as follows:

 {val: 1, freq: 1} {val: 2, freq: 2} {val: 4, freq: 1} {val: 3, freq: 4} 

Then find local minimum nodes, such as node (3 3 3 3) in 1 (2 2) 4 (3 3 3 3) 4 , that is, nodes with neighbors that have higher values. For those local minima that have an even frequency, “raise” them by applying a step. Repeat this until such local minima exist (with even frequency).

The beginning of the recursive part of the algorithm:

At both ends of the array, work inward to “raise” the values ​​if a higher neighboring neighbor has a higher value. With this rule:

 1 2 2 3 5 4 3 3 3 1 1 

completely allow. First, from the left side inward:

 1 4 5 4 3 3 3 1 1 

Then on the right side:

 1 4 6 3 2 

Please note that if there is an odd frequency (for example, for 3 above) there will be a "remainder" that cannot be increased. The rest of this rule always remains on the outside, therefore, to maximize the potential to the inside of the array.

At this point, the remaining local minima have odd frequencies. Applying a step to such a node will always leave a “remainder” (for example, higher) with the original value. This remaining node can appear anywhere, but it makes sense to consider solutions where this remainder is on the left or right side of the elevator (and not in the middle). For example:

 4 1 1 1 1 1 2 3 4 

You can enable one of the following:

 4 2 2 1 2 3 4 

Or:

 4 1 2 2 2 3 4 

1 in any second or fourth position is the aforementioned “remainder”. Obviously, in this example, the second solution is more promising. In general, the choice is obvious when on one side there is too high a value for merging, since the left largest 4 too high for five values ​​of 1 to get to. 4 looks like a wall.

When the frequency of the local minimum is unity, we can do nothing about it. It actually separates the array on the left and right sides, which do not affect each other. The same is true for the remainder element mentioned above: it splits the array into two parts that do not affect each other.

So, the next step in the algorithm is to search for such minima (when the choice is obvious), apply this step and divide the problem into two different problems that must be solved recursively (above). Therefore, in the last example, the following two problems would be solved separately:

 4 2 2 3 4 

Then the best of both solutions will be considered a common solution. In this case, it is 5 .

The most difficult part of the algorithm is the consideration of those local minima for which the choice of a place for placing the remainder is not obvious. For instance,

 3 3 1 1 1 1 1 2 3 

This can go either:

 3 3 2 2 1 2 3 3 3 1 2 2 2 3 

In this example, the end result is the same for both parameters, but in large arrays it will be less and less obvious. Therefore, both options should be investigated here. In general, you can have many of them, for example 2 in this example:

 3 1 1 1 2 3 1 1 1 1 1 3 

Each of these two minimums has two options. This is like blowing up too many possibilities for large arrays. But this is not so bad. The algorithm can take opposite options at neighboring lows and thus alternate through the entire array. Thus, preference is given to alternating sections and to receive the maximum possible value in them, while other sections are devoid of value. Now the algorithm turns the tables and switches all the options, so that partitions that were previously approved are now deprived, and vice versa. The solution to both of these alternatives is obtained by recursively solving each section, and then comparing the two “grandiose” solutions to choose the best.

Excerpt

The following is a real-time JavaScript implementation of the above algorithm. Comments are presented which we hope should make them readable.

 "use strict"; function Node(val, freq) { // Immutable plain object return Object.freeze({ val: val, freq: freq || 1, // Default frequency is 1. // Max attainable value when merged: reduced: val + (freq || 1).toString(2).length - 1 }); } function compress(a) { // Put repeated elements in a single node var result = [], i, j; for (i = 0; i < a.length; i = j) { for (j = i + 1; j < a.length && a[j] == a[i]; j++); result.push(Node(a[i], j - i)); } return result; } function decompress(a) { // Expand nodes into separate, repeated elements var result = [], i, j; for (i = 0; i < a.length; i++) { for (j = 0; j < a[i].freq; j++) { result.push(a[i].val); } } return result; } function str(a) { return decompress(a).join(' '); } function unstr(s) { s = s.replace(/\D+/g, ' ').trim(); return s.length ? compress(s.split(/\s+/).map(Number)) : []; } /* The function merge modifies an array in-place, performing a "step" on the indicated element. The array will get an element with an incremented value and decreased frequency, unless a join occurs with neighboring elements with the same value: then the frequencies are accumulated into one element. When the original frequency was odd there will be a "remainder" element in the modified array as well. */ function merge(a, i, leftWards, stats) { var val = a[i].val+1, odd = a[i].freq % 2, newFreq = a[i].freq >> 1, last = i; // Merge with neighbouring nodes of same value: if ((!odd || !leftWards) && a[i+1] && a[i+1].val === val) { newFreq += a[++last].freq; } if ((!odd || leftWards) && i && a[i-1].val === val) { newFreq += a[--i].freq; } // Replace nodes a.splice(i, last-i+1, Node(val, newFreq)); if (odd) a.splice(i+leftWards, 0, Node(val-1)); // Update statistics and trace: this is not essential to the algorithm if (stats) { stats.total_applied_merges++; if (stats.trace) stats.trace.push(str(a)); } return i; } /* Function Solve Parameters: a: The compressed array to be reduced via merges. It is changed in-place and should not be relied on after the call. stats: Optional plain object that will be populated with execution statistics. Return value: The array after the best merges were applied to achieve the highest value, which is stored in the maxValue custom property of the array. */ function solve(a, stats) { var maxValue, i, j, traceOrig, skipLeft, skipRight, sections, goLeft, b, choice, alternate; if (!a.length) return a; if (stats && stats.trace) { traceOrig = stats.trace; traceOrig.push(stats.trace = [str(a)]); } // Look for valleys of even size, and "lift" them for (i = 1; i < a.length - 1; i++) { if (a[i-1].val > a[i].val && a[i].val < a[i+1].val && (a[i].freq % 2) < 1) { // Found an even valley i = merge(a, i, false, stats); if (i) i--; } } // Check left-side elements with always increasing values for (i = 0; i < a.length-1 && a[i].val < a[i+1].val; i++) { if (a[i].freq > 1) i = merge(a, i, false, stats) - 1; }; // Check right-side elements with always increasing values, right-to-left for (j = a.length-1; j > 0 && a[j-1].val > a[j].val; j--) { if (a[j].freq > 1) j = merge(a, j, true, stats) + 1; }; // All resolved? if (i == j) { while (a[i].freq > 1) merge(a, i, true, stats); a.maxValue = a[i].val; } else { skipLeft = i; skipRight = a.length - 1 - j; // Look for other valleys (odd sized): they will lead to a split into sections sections = []; for (i = a.length - 2 - skipRight; i > skipLeft; i--) { if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) { // Odd number of elements: if more than one, there // are two ways to merge them, but maybe // one of both possibilities can be excluded. goLeft = a[i+1].val > a[i].reduced; if (a[i-1].val > a[i].reduced || goLeft) { if (a[i].freq > 1) i = merge(a, i, goLeft, stats) + goLeft; // i is the index of the element which has become a 1-sized valley // Split off the right part of the array, and store the solution sections.push(solve(a.splice(i--), stats)); } } } if (sections.length) { // Solve last remaining section sections.push(solve(a, stats)); sections.reverse(); // Combine the solutions of all sections into one maxValue = sections[0].maxValue; for (i = sections.length - 1; i >= 0; i--) { maxValue = Math.max(sections[i].maxValue, maxValue); } } else { // There is no more valley that can be resolved without branching into two // directions. Look for the remaining valleys. sections = []; b = a.slice(0); // take copy for (choice = 0; choice < 2; choice++) { if (choice) a = b; // restore from copy on second iteration alternate = choice; for (i = a.length - 2 - skipRight; i > skipLeft; i--) { if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) { // Odd number of elements alternate = !alternate i = merge(a, i, alternate, stats) + alternate; sections.push(solve(a.splice(i--), stats)); } } // Solve last remaining section sections.push(solve(a, stats)); } sections.reverse(); // put in logical order // Find best section: maxValue = sections[0].maxValue; for (i = sections.length - 1; i >= 0; i--) { maxValue = Math.max(sections[i].maxValue, maxValue); } for (i = sections.length - 1; i >= 0 && sections[i].maxValue < maxValue; i--); // Which choice led to the highest value (choice = 0 or 1)? choice = (i >= sections.length / 2) // Discard the not-chosen version sections = sections.slice(choice * sections.length/2); } // Reconstruct the solution from the sections. a = [].concat.apply([], sections); a.maxValue = maxValue; } if (traceOrig) stats.trace = traceOrig; return a; } function randomValues(len) { var a = []; for (var i = 0; i < len; i++) { // 50% chance for a 1, 25% for a 2, ... etc. a.push(Math.min(/\.1*/.exec(Math.random().toString(2))[0].length,5)); } return a; } // I/O var inputEl = document.querySelector('#inp'); var randEl = document.querySelector('#rand'); var lenEl = document.querySelector('#len'); var goEl = document.querySelector('#go'); var outEl = document.querySelector('#out'); goEl.onclick = function() { // Get the input and structure it var a = unstr(inputEl.value), stats = { total_applied_merges: 0, trace: a.length < 100 ? [] : undefined }; // Apply algorithm a = solve(a, stats); // Output results var output = { value: a.maxValue, compact: str(a), total_applied_merges: stats.total_applied_merges, trace: stats.trace || 'no trace produced (input too large)' }; outEl.textContent = JSON.stringify(output, null, 4); } randEl.onclick = function() { // Get input (count of numbers to generate): len = lenEl.value; // Generate var a = randomValues(len); // Output inputEl.value = a.join(' '); // Simulate click to find the solution immediately. goEl.click(); } // Tests var tests = [ ' ', '', '1', '1', '1 1', '2', '2 2 1 2 2', '3 1 3', '3 2 1 1 2 2 3', '5', '3 2 1 1 2 2 3 1 1 1 1 3 2 2 1 1 2', '6', '3 1 1 1 3', '3 2 1 3', '2 1 1 1 2 1 1 1 2 1 1 1 1 1 2', '3 1 2 1 4 1 2', '3 1 1 2 1 1 1 2 3', '4 2 1 2 3', '1 4 2 1 1 1 1 1 1 1', '1 5 1', ]; var res; for (var i = 0; i < tests.length; i+=2) { var res = str(solve(unstr(tests[i]))); if (res !== tests[i+1]) throw 'Test failed: ' + tests[i] + ' returned ' + res + ' instead of ' + tests[i+1]; } 
 Enter series (space separated):<br> <input id="inp" size="60" value="2 3 1 1 2 2"><button id="go">Solve</button> <br> <input id="len" size="4" value="30"><button id="rand">Produce random series of this size and solve</button> <pre id="out"></pre> 

As you can see, the program creates a reduced array with the maximum value included. In general, there can be many massive arrays that have this maximum; only one is provided.

+1
source

And O(n*m) is an algorithm of time and space, where, according to your specified limits, n <= 500 and m <= 58 (note that even for a billion elements m should be only about 60, representing the largest element ± log2(n) ). m represents the possible numbers 50 + floor(log2(500)) :

Consider the condensed sequence s = {[x, number of x's]} .

If M[i][j] = [num_j,start_idx] where num_j represents the maximum number of adjacent j ending with the index i condensed sequence; start_idx , the index where the sequence begins, or -1 if it cannot join earlier sequences; then we have the following relation:

 M[i][j] = [s[i][1] + M[i-1][j][0], M[i-1][j][1]] when j equals s[i][0] 

j greater than s[i][0] , but less than or equal to s[i][0] + floor(log2(s[i][1])) are pair conversions and a merge with an earlier sequence if this applies, with a special case, after the new account is odd:

When M[i][j][0] is odd, we do two things: first, calculate the best by looking back at the matrix for a sequence that could merge with M[i][j] or its paired descendants, and then set the bottom border to the next applicable cells in the row (which means that merging with an earlier sequence cannot occur through this cell). The reason for this is that:

  • if s[i + 1][0] > s[i][0] , then s[i + 1] can only be possible with a new partition partition s[i] ; and
  • if s[i + 1][0] < s[i][0] , then s[i + 1] can generate a lower j that will combine with the odd j from M[i] , potentially making a longer sequence.

Finally, return the largest entry in the matrix max(j + floor(log2(num_j))), for all j .

JavaScript code (counter examples will be welcome, the response limit is set to 7 for convenient visualization of the matrix):

 function f(str){ var arr = str.split(/\s+/).map(Number); var s = [,[arr[0],0]]; for (var i=0; i<arr.length; i++){ if (s[s.length - 1][0] == arr[i]){ s[s.length - 1][1]++; } else { s.push([arr[i],1]); } } var M = [new Array(8).fill([0,0])], best = 0; for (var i=1; i<s.length; i++){ M[i] = new Array(8).fill([0,i]); var temp = s[i][1], temp_odd, temp_start, odd = false; for (var j=s[i][0]; temp>0; j++){ var start_idx = odd ? temp_start : M[i][j-1][1]; if (start_idx != -1 && M[start_idx - 1][j][0]){ temp += M[start_idx - 1][j][0]; start_idx = M[start_idx - 1][j][1]; } if (!odd){ M[i][j] = [temp,start_idx]; temp_odd = temp; } else { M[i][j] = [temp_odd,-1]; temp_start = start_idx; } if (!odd && temp & 1 && temp > 1){ odd = true; temp_start = start_idx; } best = Math.max(best,j + Math.floor(Math.log2(temp))); temp >>= 1; temp_odd >>= 1; } } return [arr, s, best, M]; } // I/O var button = document.querySelector('button'); var input = document.querySelector('input'); var pre = document.querySelector('pre'); button.onclick = function() { var val = input.value; var result = f(val); var text = ''; for (var i=0; i<3; i++){ text += JSON.stringify(result[i]) + '\n\n'; } for (var i in result[3]){ text += JSON.stringify(result[3][i]) + '\n'; } pre.textContent = text; } 
 <input value ="2 2 3 3 2 2 3 3 5"> <button>Solve</button> <pre></pre> 
+1
source

Here's a brute force solution:

 function findMax(array A, int currentMax) for each pair (i, i+1) of indices for which A[i]==A[i+1] do currentMax = max(A[i]+1, currentMax) replace A[i],A[i+1] by a single number A[i]+1 currentMax = max(currentMax, findMax(A, currentMax)) end for return currentMax Given the array A, let currentMax=max(A[0], ..., A[n]) print findMax(A, currentMax) 

The algorithm ends because in each recursive call the array is compressed by 1 .

It is also clear that this is correct: we test all possible replacement sequences.

The code is very slow when the array is large and there are many replacement options, but it actually works reasonably fast on arrays with few replaceable pairs. (I will try to quantify the runtime in terms of the number of replaceable pairs.)

Naive working code in Python:

 def findMax(L, currMax): for i in range(len(L)-1): if L[i] == L[i+1]: L[i] += 1 del L[i+1] currMax = max(currMax, L[i]) currMax = max(currMax, findMax(L, currMax)) L[i] -= 1 L.insert(i+1, L[i]) return currMax # entry point if __name__ == '__main__': L1 = [2, 3, 1, 1, 2, 2] L2 = [2, 3, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2] print findMax(L1, max(L1)) print findMax(L2, max(L2)) 

The result of the first call of 4 , as expected.

The result of the second call of 5 as expected; the sequence that gives the result: 2,3,1,1,2,2,2,2,2,2,2,2,2,2, → 2,3,1,1,3,2,2,2,2 , 2.2 → 2,3,1,1,3,3,2,2,2,2, → 2,3,1,1,3,3,3,2,2-> 2,3, 1 , 1,3,3,3,3 → 2,3,1,1,4,3, → 2,3,1,1,4,4 → 2,3,1,1,5

0
source

Source: https://habr.com/ru/post/1246300/


All Articles