Lovely!

Perhaps a stylistic shift would encourage writing this sort of elegant, fusion-friendly code.

    -- Generalized version of "interact".  Encapsulates data getter & putter.
    genInteract :: IO i -> (o -> IO ()) -> ((i -> o) -> IO ())
    genInteract get put = \ f -> get >>= put . f

The intention here is that i and o are pure value types (no IO).  Solutions 2 and three use the following specialization.

    -- Lazy ByteString in and showable out
    lGetPrint :: Show o => (L.ByteString -> o) -> IO ()
    lGetPrint = genInteract L.getContents print

Then rewrite solution 2 as follows:

    main = lGetPrint f
     where
       f contents = foldl' (test k) 0 . map int . take n $ ls
         where
           (l:ls) = L.lines contents
           [n,k]  = map int (L.split ' ' l)

And solution 3 similarly.

Plug: for additional examples and a more general approach to separating out IO, my blog post " separating IO from logic -- example ", which uses the TV library.