Improve solution for day 17.

This commit is contained in:
Bert Peters
2015-12-18 13:00:52 +01:00
parent 09a25f796b
commit d7c815f0c0
2 changed files with 68 additions and 17 deletions

26
day-17/README.md Normal file
View File

@@ -0,0 +1,26 @@
# Day 17 efficient solution
Day 17 is an instance of the subset sum problem. This problem asks whether for
a (multi)set of integers *V*, there is a non-empty subset of integers summing up to
exactly *s*. This problem is NP-complete.
The brute force approach of this is trying every possible set in the powerset
of *V* to see if they match. This is inpractical however, because a powerset of
a set of size *n* contains 2<sup>2</sup> sets.
In the exercise, we have 20 buckets, and 2<sup>20</sup> is still
brute-forcable. There is a smarter approach.
We split the list of buckets in two lists of (approximately) the same size. We
then take the powersets of those two lists and compute the sum for each entry.
This leaves us with a total of 2<sup>n / 2 + 1</sup> entries. We then sort both
sublists on the total value of each entry.
Finally, we iterate of the first list, and use binary search to see whether
there is an appropriately sized entry in the second list. This gives us a final
complexity of *n* times 2<sup>*n*/2</sup>, allowing the solution to be computed
instantly.
The algorithm above can be modified to find all combinations, not just one, in
time proportional to the number of solutions. This is implemented in the final
program.

View File

@@ -1,21 +1,17 @@
from __future__ import print_function, division
import fileinput
from collections import defaultdict
import bisect
buckets = []
def value(buckets, choice):
total = 0
for value in buckets:
if choice % 2 == 1:
total += value
for line in fileinput.input():
buckets.append(int(line))
choice //= 2
def works(bucketCombination, target):
for idx, value in enumerate(buckets):
if bucketCombination % 2 == 1:
target -= value
bucketCombination = bucketCombination // 2
return target == 0
return total
def ones(x):
n = 0
@@ -27,11 +23,40 @@ def ones(x):
return n
def partition(a_list):
pivot = len(a_list) // 2
return a_list[:pivot], a_list[pivot:]
def partitionList(buckets):
result = [(value(buckets, x), ones(x)) for x in range(1 << len(buckets))]
result.sort()
return result
buckets = []
for line in fileinput.input():
buckets.append(int(line))
partition1, partition2 = partition(buckets)
values1 = partitionList(partition1)
values2 = partitionList(partition2)
possible = defaultdict(lambda: 0)
for x in range(1 << len(buckets)):
if works(x, 150):
n = ones(x)
possible[n] += 1
i = 0
target = 150
for entry in values1:
i = bisect.bisect_left(values2, (target - entry[0], 0))
while i < len(values2) and entry[0] + values2[i][0] == target:
possible[entry[1] + values2[i][1]] += 1
i += 1
print("Total possibilities:", sum(possible.values()))
print("Minimal possibilities:", possible[min(possible.keys())])
print(sum(possible[x] for x in possible), possible[min(possible.keys())])