tricky puzzle

Started by
23 comments, last by O_o 18 years, 9 months ago
To me it sounds like all you really need to do is find all the subsets, or the power set. See MathWorld. We had to do that in class once. The following code is straight from my assignment:

//------------------// Returns the power set //------------------Set * Set::operator ~ (void){	// gets the new size of 2^n and adds one to	// account for the element that stores the size	int nPowSize = (2 << (this->m_nSize-1)) + 1;	Set *Pow = new Set[nPowSize];		// store size in the first element of the first	// set in the array	Set cSet;	cSet.Add(nPowSize);	Pow[0] = cSet;		// go through the size of the power set	// and add the element in position j to the 	// current set if its bit is 1	for(int i=0; i < nPowSize; ++i)	{		for(int j=0; j < this->m_nSize; ++j)		{			if(i & (1<<j))			{				Pow[i+1].Add(this->GetElement(j));							}		}			}			return Pow;} // operator ~


We had to write our own set for this, and I know the set implementation isn't the best. The size of the powerset is 2^n, but I had to add the size in there, so that takes up the extra element. Remember that Pow[] returns a set, since it is a set of sets. Then you can go through those subsets in the Pow[] and add up the sum of the elements to check if it is within a reasonable tolerance to an integer.

[edit]
A little more explaination. What this basically does is count in binary up to the size of the set. Say you have a set of size 3, which is {2,3,4}. The size of the powerset is 8;

000 - {}
001 - {4}
010 - {3}
100 - {2}
011 - {3,4}
101 - {2,4}
110 - {2,3}
111 - {2,3,4}

although in the code, the actual algorithm adds it in this order.

000 - {}
100 - {2}
010 - {3}
110 - {2,3}
001 - {4}
101 - {2,4}
011 - {3,4}
111 - {2,3,4}

[Edited by - njpaul on July 1, 2005 2:37:36 PM]
Advertisement
paulbird says:
Quote:
Obviously one way of doing this is to simply try out every posible subset .... I wonder if there is a quicker trick to doing this.


njpaul says:
Quote:
To me it sounds like all you really need to do is find all the subsets
Quote:
Obviously one way of doing this is to simply try out every posible subset .... I wonder if there is a quicker trick to doing this.


If you have to go through every subset, there is no faster way. All you can hope for is an optimized algorithm.
You don't have to go through every possible subset. I'd say a very large portion of the subsets can be eliminated with some cleverness and a little bit of simple logic.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

True, you don't have to go though every subset, but you still have to generate them. You can easily skip the empty set.
let dec f = f -. floor f;;type result = None | Found of float list;;let solve nums =    let lst = ref []    and result = ref None in        let update n =        let todo = ref !lst        and adds = ref [] in        while !todo != [] && !result == None do            let (x, terms) = List.hd !todo in            let x' = dec (x +. n)            and terms' = n :: terms in            print_float x'; print_newline ();            if abs_float x' < sqrt epsilon_float            then result := Found terms'            else (                   adds := (x', terms') :: !adds;                   todo := List.tl !todo                 )        done;        lst := (n, [n]) :: (!adds @ !lst)    in    let todo = ref nums in    while !todo != [] && !result == None do        update (List.hd !todo);        todo := List.tl !todo;    done;    !result;;


Given a list L of reachable values and a number N, a new list L' of reachable values is constructed with L union { x + N | x in L }. The algorithm stops as soon as it hits an integer (x % 1 == +-eps).
Quote:Original post by SamLowry
*** Source Snippet Removed ***

Given a list L of reachable values and a number N, a new list L' of reachable values is constructed with L union { x + N | x in L }. The algorithm stops as soon as it hits an integer (x % 1 == +-eps).
Sounds nice, although I can't immediately tell if it helps a whole lot.

It would be nice if paulbird confirmed that this wasn't homework btw. It's worded very much as though it is.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
The solution is to use dynamic programming. Sort the numbers from largest to smallest, and keep a table of all possible sums that you have found so far. Then, iterate through the numbers. For each number, add it to each sum found so far. If it's an integer, print out the set and exit. If it's already in the set, do nothing, otherwise add it to the set.

In the worst case, this will still run in exponential time (and take exponential memory), if all possible subset sums are different, but in most practical examples it will work pretty well.
Quote:Original post by Aprosenf
The solution is to use dynamic programming. Sort the numbers from largest to smallest, and keep a table of all possible sums that you have found so far. Then, iterate through the numbers. For each number, add it to each sum found so far. If it's an integer, print out the set and exit. If it's already in the set, do nothing, otherwise add it to the set.

In the worst case, this will still run in exponential time (and take exponential memory), if all possible subset sums are different, but in most practical examples it will work pretty well.


And that's exactly what the OCaml code posted above does. (except the sorting)
Quote:"Decide whether any subset of these numbers adds up to an intger."


My emphisis. Assuming we approach the problem of "try to find a subset", it might be possible to speed things up based on digit math. E.g. if you have a set like so:
6.234655.234537.348582.243885.349938

You can discard the last number, as there's no way you can supplement the 8 with another number to form a zero with carry. Similarly, if you have a set like so:
6.234655.234537.348582.243885.3499382.824762

You'd be able to know that you must include either both of the last two, or neither. Things get much more complicated as the number of numbers with a nonzero digit in that place increase, but it might be possible.

Of note is that this won't work very well if the results are allowed to be somewhat inexact - e.g. 0.50001 + 0.50000 = 0.50001 - which means the algorithm would discard the result due to the one. There's probably a similar algorithm that depends on results being within a delta, or simply optionally adding a fake number within the epsilon to round... (edit: actual FP math eps. would involve some O(N!) algorithms it seems, and probably more complicated than the brute force's O(N!) method))

I may fiddle around with this for awhile for the heck of it, but I won't be posting the actual implementation due to homework-smell, at least not for now.

EDIT: I've decided that this method is probably not very helpful. It still ramps ala O(N!). It may help for smaller sets of data, but with the repeated resortings by digit and whatnot, I doubt it. C'est la vie...

[Edited by - MaulingMonkey on July 2, 2005 8:56:08 AM]

This topic is closed to new replies.

Advertisement