--djiVxcjPnSZsftdlA/c
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

Okay, I'm pretty sure that's 48 hours now.  Here's my shot at a
solution.

Random notes:

* The algorithm isn't very bright -- it's basically a mangled "first
fit" approach which does a barely tolerable job.  I was hoping to come
up with something smarter, but I suck at maths so a good PTAS for this
has eluded me.  The problem is essentially the 2d version of
bin-packing, so there should be one...

* Part of the algorithm's stupidity is due to the aggressive pruning I
have to do to limit the number of solutions under consideration; if I
didn't prune so brutally, there'd be a sort of combinatorial explosion
in the worst cases.  For pruning, I favor the solutions which succeed in
placing boxes early, and those which minimize the fragmentation of the
free space.

* Rather than worry about inter-box padding directly, I just pad the
size of the boxes and the trunk by 1 when I read the initial input, then
trim off the padding when I display the result.

* The state of a trunk is represented as a list of boxes and a list of
overlapping rectangles of free space.  When a new box is placed in the
trunk, it's carved out of each of these free space rectangles via
Box#eliminate, whose result is a list of new overlapping rectangles
covering the remaining space.

* I aimed for a more or less "functional" style, in the sense that I
tried to avoid side-effects or mutation.  The really observant (or those
who have read my incomplete monad tutorial) may notice that I used a
monad built on Array to get non-determinism.

* Performance-wise, profiling shows that most of the time is spent in
Box#intersects? and Box#eliminate.  Maybe it needs a better data
structure for representing free space...

-mental

Solution follows...

--- 8< ------------ 8< ---
#!/usr/bin/ruby

MAX_SOLUTIONS = 100

class Array
  def pass( &block )
    map( &block ).inject( [] ) { |a, b| a.concat b }
  end
  def minimize
    minimum = map { |x| yield x }.min
    reject { |x| yield( x ) > minimum }
  end
end

module Area
  def area
    @area ||= width * height
  end
  def fits?( space )
    space.width >= width && space.height >= height
  end
end

class Box
  include Area
  
  attr_reader :min_x, :min_y, :max_x, :max_y

  def initialize( min_x, min_y, max_x, max_y )
    @min_x, @min_y, @max_x, @max_y = min_x, min_y, max_x, max_y
  end

  def intersects?( box )
    box.min_x < @max_x && box.min_y < @max_y &&
      box.max_x > @min_x && box.max_y > @min_y
  end
  def eliminate( box )
    return [self] unless self.intersects? box
    remaining = []
    remaining << Box.new( @min_x, @min_y, box.min_x, @max_y ) \
      if @min_x < box.min_x
    remaining << Box.new( @min_x, @min_y, @max_x, box.min_y ) \
      if @min_y < box.min_y
    remaining << Box.new( box.max_x, @min_y, @max_x, @max_y ) \
      if box.max_x < @max_x
    remaining << Box.new( @min_x, box.max_y, @max_x, @max_y ) \
      if box.max_y < @max_y
    remaining
  end
  
  def width
    @width ||= @max_x - @min_x
  end
  def height
    @height ||= @max_y - @min_y
  end
end

class Dimensions
  include Area
  
  attr_reader :width, :height

  def self.new_from_string( string )
    string =~ /(\d+)x(\d+)/
    self.new( $2.to_i, $1.to_i )
  end

  def initialize( width, height )
    @width, @height = width, height
  end
  
  def rotate
    @rotated ||= Dimensions.new( @height, @width )
  end
  def pad( n )
    Dimensions.new( @width + n, @height + n )
  end
end

class Trunk
  def self.new_from_area( area )
    self.new( area, [], [ Box.new( 0, 0, area.width, area.height ) ] )
  end

  def initialize( area, contents, remaining )
    @area, @contents, @remaining = area, contents, remaining
  end

  def empty? ; @contents.empty? ; end
  def fragmentation ; @remaining.length ; end
  
  def cram( thingy )
    @remaining.map { |space|
      next nil unless thingy.fits? space
      box = Box.new( space.min_x,
                     space.min_y,
                     space.min_x + thingy.width,
                     space.min_y + thingy.height )
      Trunk.new( @area, @contents + [box],
                 @remaining.pass { |space| space.eliminate box } )
    }.compact
  end
  
  def pretty_print
    all = @contents + @remaining
    width = @area.width - 1
    height = @area.height - 1
    aa = ( [ "." * width ] * height ).map { |x| x.dup }
    @contents.each do |box|
      for y in box.min_y...( box.max_y - 1 )
        run_length = box.width - 1
        aa[y][box.min_x, run_length] = "*" * run_length
      end
    end
    aa.each { |line| puts line }
  end
end

def pack_trunk( trunk_area, box_areas )
  box_areas.inject( [[ Trunk.new_from_area( trunk_area ), [] ]] ) do
    |solutions, box|

    solutions.pass { |trunk, leftovers|
      packings = trunk.cram( box ) + trunk.cram( box.rotate )
      if packings.empty?
        raise "One of them boxes is too big!" if trunk.empty?
        [[ trunk, leftovers + [box] ]]
      else
        packings.map { |packing| [ packing, leftovers ] }
      end
    }.minimize {
      |trunk, leftovers| leftovers.length
    }.minimize {
      |trunk, leftovers| trunk.fragmentation
    }[0, MAX_SOLUTIONS]
  end
end

def pack_trunks( trunks, trunk_area, box_areas )
  return [trunks] if box_areas.empty?

  pack_trunk( trunk_area, box_areas ).minimize {
    |trunk, leftovers| leftovers.length
  }.pass { |trunk, leftovers|
    pack_trunks( trunks + [trunk], trunk_area, leftovers )
  }
end

def solve( trunk_area, box_areas )
  box_areas = box_areas.sort_by { |box| box.area }.reverse
  pack_trunks( [], trunk_area, box_areas ).minimize {
    |trunks| trunks.length
  }.first
end

trunk_area = Dimensions.new_from_string( gets ).pad( 1 )
box_areas = gets.split.map { |s|
  Dimensions.new_from_string( s ).pad( 1 )
}
solve( trunk_area, box_areas ).each do |trunk|
  puts
  trunk.pretty_print
end


--djiVxcjPnSZsftdlA/c
Content-Type: application/pgp-signature; name=signature.asc
Content-Description: This is a digitally signed message part

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)

iD8DBQBDypzVcUNIGiXCc4MRAj+IAJ97Y2WqVO1lapeJg72zn4fd7rGyjACePB66
ExBGfObBmspnrDg41eyJhqEAN
-----END PGP SIGNATURE-----

--djiVxcjPnSZsftdlA/c--