This is a variant of the 0-1 knapsack problem. This problem is NP-hard, so there is not much hope to find a solution in polynomial time, but there exists a solution in pseudo-polynomial time which makes this problem rather easy (in the world of complex problems).
The algorithm works as follows:
- Start with a collection (for instance a set) containing the tuple
<0,0,0>
.
- For each carton
<a',b',c'>
: iterate over the all tuples <a,b,c>
in the collection and add <a+a',b+b',c+c'>
to the collection, ensure that duplicates are not added. Don't add tuples where one or more elements have exceeded the corresponding target value.
- If the given collection contains the target values after the algorithm, print "yes", otherwise "no".
Optionally but strongly advisable lower-bound elimination: you can also perform lookaheads and for instance eliminate all values that will never reach the given target anymore (say you can at most add 20
apples, then all values less than 80
apples can be eleminated).
Concept 1 (Lowerbound): Since you add values of tuples together, you now that if there are tuples <a0,a1,a2>
and <b0,b1,b2>
left, adding these will at most increase a tuple with <a0+b0,a1+b1,a2+b2>
. Now say the target is <t0,t1,t2>
then you can safely eliminate a tuple <q0,q1,q2>
if q0+a0+b0 < t0
(generalize to other tuple elements), since even if you can add the last tuples, it will never reach the required values. The lower bound is thus <t0-a0-b0,t1-a1-b1,t2-a2-b2>
. You can generalize this for n tuples.
So first you add up all provided tuples together (for the second instance, that's <140,160,230>
) and than subtract that from the target (the result is thus: <-40,-60,-130>
). Each iteration, the lower bound is increased with that carton, so after the first iteration, the result for the second example is (<-40+40,-60+70,-130+30>
or <0,10,-100>
).
The time complexity is however O(ta^3 tb^3 tc^3) with ta, tb and tc the target values.
Example 1 (high level on the two given testcases):
INPUT
100 100 100
3
10 10 40
10 30 10
10 60 50
The set starts with {<0,0,0>}
, after each iteration we get:
{<0,0,0>}
;
{<0,0,0>,<10,10,40>}
;
{<0,0,0>,<10,10,40>,<10,30,10>,<20,40,50>}
; and
{<0,0,0>,<10,10,40>,<10,30,10>,<20,40,50>,<10,60,50>,<10,60,50>,<20,70,90>,<30,100,100>}
, thus fail.
With underbound-elimination:
{<0,0,0>}
, lowerbound <100-30,100-100,100-100>=<70,0,0>
thus eliminate <0,0,0>
.
{}
thus print "no".
Example 2
INPUT
100 100 100
5
40 70 30
30 10 40
20 20 50
10 50 90
40 10 20
With lower-bound elimination:
{<0,0,0>}
lower bound: <-40,-60,-130>
thus ok.
{<0,0,0>,<40,70,30>}
lower bound: <0,10,-100>
(eliminate <0,0,0>
because second conflicts).
{<40,70,30>,<70,80,70>}
lower bound: <30,20,-60>
(no elimination).
{<40,70,30>,<70,80,70>,<60,90,80>,<90,100,120>}
lower bound: <50,40,-10>
(eliminate <40,70,30>
) upper eliminate <90,100,120>
.
{<70,80,70>,<60,90,80>,<80,130,160>,<70,140,170>}
lower bound: <60,90,80>
(eliminate <70,80,70>
) upper eliminate <80,130,160>
and <70,140,170>
.
{<60,90,80>,<100,100,100>}
lower bound: <100,100,100>
(eliminate <60,90,80>
).
{<100,100,100>}
thus "yes".
Haskell program
I've implemented a (not that efficient, but proof of concept) Haskell program that does the trick for an arbitrary tuple-length:
import qualified Data.Set as Set
tupleSize :: Int
tupleSize = 3
group :: Int -> [a] -> [[a]]
group _ [] = []
group n l = take n l : group n (drop n l)
empty :: Int -> Set.Set [Int]
empty n = Set.fromList [replicate n 0]
solve :: [Int] -> [[Int]] -> Bool
solve t qs = Set.member t $ mix t (lowerBound t qs) qs $ empty $ length t
lowerBound :: [Int] -> [[Int]] -> [Int]
lowerBound = foldl (zipWith (-))
lowerCheck :: [Int] -> [Int] -> Bool
lowerCheck l x = and $ zipWith (<=) l x
targetCheck :: [Int] -> [Int] -> Bool
targetCheck t x = and $ zipWith (>=) t x
takeout :: Int -> [a] -> [a]
takeout _ [] = []
takeout i (h:hs) | i == 0 = hs
| otherwise = h : takeout (i-1) hs
mix :: [Int] -> [Int] -> [[Int]] -> Set.Set [Int] -> Set.Set [Int]
mix _ _ [] s = s
mix t l (q:qs) s = mix t (zipWith(+) l q) qs $ Set.filter (lowerCheck l) $ Set.union s $ Set.filter (targetCheck t) $ Set.map (zipWith (+) q) s
reply :: Bool -> String
reply True = "yes"
reply False = "no"
main = interact $ \x -> let tuples = group tupleSize $ takeout tupleSize $ map read (words x) in reply $ solve (head tuples) (tail tuples)
You can compile an run it using:
ghc file.hs
./file < input
Conclusion: Although the worst-case behavior can be hard, the second example shows that the problem can be solve efficiently for some cases.