in reply to Challenge: Number of unique ways to reach target sum
Here's the code I used:
num_ways($N,$S,$T) computes the number of ways to pick a S (distinct) elements from the range {1..N} whose sum is T. Here's how I compute it recursively:use List::Util 'min'; use POSIX 'ceil'; sub num_ways { my ($N, $S, $T) = @_; return 1 if $S == 0 and $T == 0; return 0 if $N < 1 or $S < 1 or $T < 1; my $min = (2*$T-$S+$S**2)/(2*$S); ## more correctly, ceil() of thi +s my $max = min( $T-(($S-1)*$S/2), $N ); my $sum = 0; for my $K ($min .. $max) { $sum += num_ways( $K-1, $S-1, $T-$K ); } return $sum; } use Memoize; memoize 'num_ways'; print num_ways(100, 10, 667), $/;
Now, if (N,S,T) don't comprise such a base case, then let's look at what numbers could possibly be the largest in a desired subset. We'll use K for the largest value of a subset. The two extremes are:
1 + 2 + 3 + ... + (S-1) + K = TSolving for K at these two extremes tells us that the largest element of one of these subsets must be between
(K-S+1) + ... + (K-2) + (K-1) + K = T
T-((S-1)S/2) ... and ... (2T-S+S2)/2SThis significantly restricts the number of subcases we have to look at (of course K has to be smaller than N as well, by the definition of the problem). So now, we let K take on every value in this range. For each value, we suppose K is the largest element in the subset. Then to complete the subset, we need to choose S-1 more items from the range {1 .. K-1} (since K must remain the largest) whose sum is T-K. This is where the recursive call within the for-loop comes from. Adding these possibilities up over all such valid K, we get our answer.
The function is also memoized for speed. When we do that, we need to compute at most N*S*T subproblems to get the value of num_ways(N,S,T). This isn't bad for the small numbers posed by Limbic~Region. In fact, this code returns an answer in less than 6 seconds on my computer.
The code can be made to not only count the solutions, but to iterate over them as well (using a callback & accumulation of the solution):
But this takes a lot longer, as memoizing does you no good.sub num_ways { my ($N, $S, $T, $callback, @so_far) = @_; return $callback->(@so_far) if $S == 0 and $T == 0; return if $N < 1 or $S < 1 or $T < 1; my $min = (2*$T-$S+$S**2)/(2*$S); my $max = min( $T-(($S-1)*$S/2), $N ); for my $K ($min .. $max) { num_ways( $K-1, $S-1, $T-$K, $callback, (@so_far, $K) ); } } num_ways( 100, 10, 667, sub { print "@_\n" } );
blokhead
|
---|
Replies are listed 'Best First'. | |
---|---|
Re^2: Challenge: Number of unique ways to reach target sum
by fergal (Chaplain) on Feb 15, 2006 at 01:50 UTC | |
by fergal (Chaplain) on Feb 15, 2006 at 03:03 UTC | |
Re^2: Challenge: Number of unique ways to reach target sum
by Limbic~Region (Chancellor) on Feb 14, 2006 at 15:51 UTC | |
by fergal (Chaplain) on Feb 15, 2006 at 01:51 UTC | |
by Limbic~Region (Chancellor) on Feb 15, 2006 at 13:19 UTC | |
by fergal (Chaplain) on Feb 15, 2006 at 15:54 UTC | |
by Limbic~Region (Chancellor) on Feb 15, 2006 at 18:38 UTC | |
|