The minimum cost is 125 (credits to Ben Barden and Zizy Archer), with steps as follows:
Input: 18 1 16 19 6 22 14 15 2 12 27 18 11 1 14 9 23 1
Minimum cost: 125
Step 1: 1 2 5 10 1 6 Buy 1 2 5 10 1 6 (cost: 25, total: 25)
Step 2: 3 4 7 12 3 8 Increment 2 times (cost: 4, total: 29)
Step 3: 3 4 7 12 3 8 1 Buy 1 (cost: 1, total: 30)
Step 4: 3 1 4 7 12 3 8 Swap them into place (cost: 5, total: 35)
Step 5: 5 3 6 9 14 5 10 Increment 2 times (cost: 4, total: 39)
Step 6: 5 3 6 9 14 5 10 1 2 1 Buy 1 2 1 (cost: 4, total: 43)
Step 7: 5 3 6 9 1 2 14 5 1 10 Swap them into place (cost: 7, total: 50)
Step 8: 7 5 8 11 3 4 16 7 3 12 Increment 2 times (cost: 4, total: 54)
Step 9: 7 5 8 11 3 4 16 7 3 12 1 Buy 1 (cost: 1, total: 55)
Step 10: 7 5 8 11 3 4 1 16 7 3 12 Swap them into place (cost: 4, total: 59)
Step 11: 8 6 9 12 4 5 2 17 8 4 13 Increment 1 times (cost: 2, total: 61)
Step 12: 8 6 9 12 4 5 2 17 8 4 13 1 Buy 1 (cost: 1, total: 62)
Step 13: 8 6 9 12 4 5 2 17 8 1 4 13 Swap them into place (cost: 2, total: 64)
Step 14: 10 8 11 14 6 7 4 19 10 3 6 15 Increment 2 times (cost: 4, total: 68)
Step 15: 10 8 11 14 6 7 4 19 10 3 6 15 1 Buy 1 (cost: 1, total: 69)
Step 16: 10 8 11 14 6 7 4 19 10 3 6 1 15 Swap them into place (cost: 1, total: 70)
Step 17: 13 11 14 17 9 10 7 22 13 6 9 4 18 Increment 3 times (cost: 6, total: 76)
Step 18: 13 11 14 17 9 10 7 22 13 6 9 4 18 1 Buy 1 (cost: 1, total: 77)
Step 19: 13 11 14 1 17 9 10 7 22 13 6 9 4 18 Swap them into place (cost: 10, total: 87)
Step 20: 18 16 19 6 22 14 15 12 27 18 11 14 9 23 Increment 5 times (cost: 10, total: 97)
Step 21: 18 16 19 6 22 14 15 12 27 18 11 14 9 23 1 2 1 1 Buy 1 2 1 1 (cost: 5, total: 102)
Step 22: 18 1 16 19 6 22 14 15 2 12 27 18 11 1 14 9 23 1 Swap them into place (cost: 23, total: 125)
Note that the result is a generalization to the "swap 1, sell, then decrement" backward approach. For this particular question, using "swap 1, sell, then decrement" works. However, it is not necessarily the case, as can be seen in the following example (credits to Ben Barden):
Input: 1 1 1 1 2 2 2 2 34 34 34
Minimum cost: 105
Step 1: 1 1 1 Buy 1 1 1 (cost: 3, total: 3)
Step 2: 34 34 34 Increment 33 times (cost: 66, total: 69)
Step 3: 34 34 34 1 1 1 1 2 2 2 2 Buy 1 1 1 1 2 2 2 2 (cost: 12, total: 81)
Step 4: 1 1 1 1 2 2 2 2 34 34 34 Swap them into place (cost: 24, total: 105)
Also another example:
Input: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99
Minimum cost: 277
Step 1: 1 1 1 Buy 1 1 1 (cost: 3, total: 3)
Step 2: 90 90 90 Increment 89 times (cost: 178, total: 181)
Step 3: 90 90 90 1 Buy 1 (cost: 1, total: 182)
Step 4: 1 90 90 90 Swap them into place (cost: 3, total: 185)
Step 5: 10 99 99 99 Increment 9 times (cost: 18, total: 203)
Step 6: 10 99 99 99 1 1 1 1 3 3 3 5 5 5 10 Buy 1 1 1 1 3 3 3 5 5 5 10 (cost: 38, total: 241)
Step 7: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99 Swap them into place (cost: 36, total: 277)
Below is the code (Python 3.7):
# Import statements
import sys
from argparse import ArgumentParser
from itertools import combinations
EXHAUSTIVE = 'exhaustive'
INCREMENTAL = 'incremental'
THRESHOLD = 'threshold'
def format_beads(beads):
result = ''
for bead in beads:
result += f'{bead:#3d}'
return result
def get_min_cost(beads, final_len, heuristics=INCREMENTAL):
min_cost = sum(beads)
if len(beads) > 0:
min_operations = [f'{{:{3*final_len}s}}\tBuy {" ".join(map(str, beads))} (cost: {min_cost}, total: {min_cost})'.format(format_beads(beads))]
else:
min_operations = []
# If there are only 2 beads, just buy them
if len(beads) <= 2:
return min_cost, min_operations
increment_cost = 0
increment_operation = None
low = min(beads)
if low > 1:
diff = low-1
increment_cost += 2*diff
increment_operation = f'{{:{3*final_len}s}}\tIncrement {diff} times (cost: {2*diff}, total: ###)'.format(format_beads(beads))
beads = [bead-diff for bead in beads]
# Now lowest bead is 1, and at least of length 3
if heuristics == EXHAUSTIVE:
def generate_partitions():
for lower_size in range(0, len(beads)):
candidates = [idx for idx, bead in enumerate(beads) if bead > 1]
for lower_idx in combinations(candidates, lower_size):
lower_idx = set(lower_idx)
higher = [bead for (i, bead) in enumerate(beads) if i not in lower_idx and bead != 1]
lower = [(i, bead) for (i, bead) in enumerate(beads) if i in lower_idx or bead == 1]
yield (higher, lower)
elif heuristics == INCREMENTAL:
def generate_partitions():
marked_count = 0
higher = []
lower = []
for i, bead in enumerate(beads):
if bead-1 <= marked_count:
lower.append((i, bead))
marked_count += 1
else:
higher.append(bead)
yield (higher, lower)
yield ([], list(enumerate(beads)))
else:
def generate_partitions():
for threshold in sorted(set(beads)):
higher = [bead for bead in beads if bead > threshold]
lower = [(i, bead) for (i, bead) in enumerate(beads) if bead <= threshold]
yield (higher, lower)
for higher, lower in generate_partitions():
num_higher = len(higher)
cur_cost, cur_operations = get_min_cost(higher, final_len, heuristics)
buy_cost = 0
swap_cost = 0
for cur, (orig, bead) in enumerate(lower):
buy_cost += bead
swap_cost += cur + num_higher - orig
cur_cost += buy_cost
cur_operations.append(f'{{:{3*final_len}s}}\tBuy {" ".join(map(lambda x:str(x[1]), lower))} (cost: {sum(bead for i, bead in lower)}, total: {cur_cost})'.format(format_beads(higher+[bead for i, bead in lower])))
if swap_cost > 0:
cur_cost += swap_cost
cur_operations.append(f'{{:{3*final_len}s}}\tSwap them into place (cost: {swap_cost}, total: {cur_cost})'.format(format_beads(beads)))
if cur_cost < min_cost:
min_cost = cur_cost
min_operations = cur_operations
# Since we are working backwards, need to add this increment after all previous operations are added
if increment_cost:
min_cost += increment_cost
min_operations.append(increment_operation.replace('###', str(min_cost)))
return min_cost, min_operations
def main(args=None):
default = [18,1,16,19,6,22,14,15,2,12,27,18,11,1,14,9,23,1]
parser = ArgumentParser(description='')
parser.add_argument('beads', type=int, nargs='*',
help='The list of beads')
parser.add_argument('--heuristics', choices=['threshold', 'incremental', 'exhaustive'])
args = parser.parse_args(args)
beads = args.beads
if not beads:
beads = default
heuristics = args.heuristics
cost, operations = get_min_cost(beads, final_len=len(beads), heuristics=heuristics)
print(f'Input: {" ".join(map(str, beads))}')
print(f'Minimum cost: {cost}')
for step, operation in enumerate(operations):
print(f'Step {step+1:2d}: {operation}')
if __name__ == '__main__':
main()
The Idea
The main idea is to split the sequence into two sub-sequences (not necessarily contiguous), put them side-by-side, then work backwards using the standard "swap, sell, decrement" on the first, and use "swap and sell" on the second part. To split the sequence into two sub-sequences, I consider all beads with value <= threshold to be put in the second sub-sequence, and iterate through all possible thresholds. To solve the first part, note that it is exactly the same as the original problem, but with less beads, so we can recursively call on our function here.
I was inspired mostly by Jeremy's answer that seems to do swap of non-1s, even though it turned out that his answer can be converted into a pure swap, sell, and decrement approach, ever buying and swapping only 1's.
The Heuristics
Now, we need to define how to split the sequence into the two sub-sequences.
In my code, I implemented this in the generate_partitions
function, which can be replaced with any heuristics we want.
It turned out that as Ben Barden mentioned, and as in Zizy Archer's answer, the heuristics that results in the optimal partitioning function is to put into the second sub-sequence those numbers which have at least that many numbers to its left (including itself) which are included in the second sub-sequence. See Zizi's answer for more details. So all numbers in 1,1,3
and 1,1,2,4
should be bought directly instead of swapped and incremented.
However, at some point, the cost of swapping out those numbers from the full sequence will outweigh the cost of buying them directly. So we need to consider both cases (use the above heuristics or simply buy everything), taking the minimum.
Original answer
I wrote a recursive program to solve this, and got essentially the same answer as Jeremy Dover, even though I didn't specifically try to follow that heuristics (see second example below, as cleverly pointed out by Ben Barden in comments). Note that I still use some heuristics (see explanation at the end).
Here is the output:
Input: 18 1 16 19 6 22 14 15 2 12 27 18 11 1 14 9 23 1
Minimum cost: 125
Step 1: 1 2 5 10 1 6 Buy 1 2 5 10 1 6 (cost: 25, total: 25)
Step 2: 3 4 7 12 3 8 Increment 2 times (cost: 4, total: 29)
Step 3: 3 4 7 12 3 8 1 Buy 1 (cost: 1, total: 30)
Step 4: 3 1 4 7 12 3 8 Swap them into place (cost: 5, total: 35)
Step 5: 4 2 5 8 13 4 9 Increment 1 times (cost: 2, total: 37)
Step 6: 4 2 5 8 13 4 9 1 Buy 1 (cost: 1, total: 38)
Step 7: 4 2 5 8 1 13 4 9 Swap them into place (cost: 3, total: 41)
Step 8: 5 3 6 9 2 14 5 10 Increment 1 times (cost: 2, total: 43)
Step 9: 5 3 6 9 2 14 5 10 1 1 Buy 1 1 (cost: 2, total: 45)
Step 10: 5 3 6 9 1 2 14 5 1 10 Swap them into place (cost: 5, total: 50)
Step 11: 7 5 8 11 3 4 16 7 3 12 Increment 2 times (cost: 4, total: 54)
Step 12: 7 5 8 11 3 4 16 7 3 12 1 Buy 1 (cost: 1, total: 55)
Step 13: 7 5 8 11 3 4 1 16 7 3 12 Swap them into place (cost: 4, total: 59)
Step 14: 8 6 9 12 4 5 2 17 8 4 13 Increment 1 times (cost: 2, total: 61)
Step 15: 8 6 9 12 4 5 2 17 8 4 13 1 Buy 1 (cost: 1, total: 62)
Step 16: 8 6 9 12 4 5 2 17 8 1 4 13 Swap them into place (cost: 2, total: 64)
Step 17: 10 8 11 14 6 7 4 19 10 3 6 15 Increment 2 times (cost: 4, total: 68)
Step 18: 10 8 11 14 6 7 4 19 10 3 6 15 1 Buy 1 (cost: 1, total: 69)
Step 19: 10 8 11 14 6 7 4 19 10 3 6 1 15 Swap them into place (cost: 1, total: 70)
Step 20: 13 11 14 17 9 10 7 22 13 6 9 4 18 Increment 3 times (cost: 6, total: 76)
Step 21: 13 11 14 17 9 10 7 22 13 6 9 4 18 1 Buy 1 (cost: 1, total: 77)
Step 22: 13 11 14 1 17 9 10 7 22 13 6 9 4 18 Swap them into place (cost: 10, total: 87)
Step 23: 17 15 18 5 21 13 14 11 26 17 10 13 8 22 Increment 4 times (cost: 8, total: 95)
Step 24: 17 15 18 5 21 13 14 11 26 17 10 13 8 22 1 Buy 1 (cost: 1, total: 96)
Step 25: 17 15 18 5 21 13 14 1 11 26 17 10 13 8 22 Swap them into place (cost: 7, total: 103)
Step 26: 18 16 19 6 22 14 15 2 12 27 18 11 14 9 23 Increment 1 times (cost: 2, total: 105)
Step 27: 18 16 19 6 22 14 15 2 12 27 18 11 14 9 23 1 1 1 Buy 1 1 1 (cost: 3, total: 108)
Step 28: 18 1 16 19 6 22 14 15 2 12 27 18 11 1 14 9 23 1 Swap them into place (cost: 17, total: 125)
My original answer uses the heuristics that all numbers below a threshold should be swapped out together.
This might not necessarily be the case. For example, using that heuristics on the following we get:
Input: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99
Minimum cost: 278
Step 1: 1 1 1 Buy 1 1 1 (cost: 3, total: 3)
Step 2: 90 90 90 Increment 89 times (cost: 178, total: 181)
Step 3: 90 90 90 1 1 Buy 1 1 (cost: 2, total: 183)
Step 4: 1 1 90 90 90 Swap them into place (cost: 6, total: 189)
Step 5: 10 10 99 99 99 Increment 9 times (cost: 18, total: 207)
Step 6: 10 10 99 99 99 1 1 1 1 3 3 3 5 5 5 Buy 1 1 1 1 3 3 3 5 5 5 (cost: 28, total: 235)
Step 7: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99 Swap them into place (cost: 43, total: 278)
Now, there is actually a better solution with cost 277 (this I obtained by going through all possible sub-sequence with the --remove_heuristics
flag, so this should be optimal):
Input: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99
Minimum cost: 277
Step 1: 1 1 1 Buy 1 1 1 (cost: 3, total: 3)
Step 2: 90 90 90 Increment 89 times (cost: 178, total: 181)
Step 3: 90 90 90 1 Buy 1 (cost: 1, total: 182)
Step 4: 1 90 90 90 Swap them into place (cost: 3, total: 185)
Step 5: 10 99 99 99 Increment 9 times (cost: 18, total: 203)
Step 6: 10 99 99 99 1 1 1 1 3 3 3 5 5 5 10 Buy 1 1 1 1 3 3 3 5 5 5 10 (cost: 38, total: 241)
Step 7: 1 1 1 10 1 3 3 3 5 5 5 10 99 99 99 Swap them into place (cost: 36, total: 277)
Note that the first 10 was created using increment, while the second 10 is just bought. This is not possible with the heuristics, since both 10's will have to be bought or both incremented using the first heuristics.