Ruby, 228 bytes * 21895 =4992060
->n{a=(0..n*2).map{$b=' '*n}
g=0
m=n*2
(n**0.5).to_i.downto(1){|i|n%i<1&&(m=[m,n+h=n/i].min
g+=h+1
g<m+2?(a[g-h-1,1]=(1..h).map{?**i+$b}):(x=(m-h..m).map{|j|r=a[j].rindex(?*);r ?r:0}.max
(m-h+1..m).each{|j|a[j][x+2]=?**i}))}
a}
Several changes from ungolfed code. Biggest one is change of meaning of variable m
from the height of the squarest rectangle, to the height of the squarest rectangle plus n
.
Trivially, *40
has been changed to *n
which means a lot of unnecessary whitespace at the right for large n
; and -2
is changed to 0
which means rectangles plotted across the bottom always miss the first two columns (this results in poorer packing for numbers whose only factorization is (n/2)*2
)
Explanation
I finally found time to get back to this.
For a given n
the smallest field must have enough space for both the longest rectangle 1*n
and the squarest rectangle x*y
. It should be apparent that the best layout can be achieved if both rectangles have their long sides oriented in the same direction.
Ignoring the requirement for whitespace between the rectangles, we find that the total area is either (n+y)*x = (n+n/x)*x
or n*(x+1)
. Either way, this evaluates to n*x + n
. Including the whitespace, we have to include an extra x
if we place the rectangles end to end, or n
if we place the rectangles side by side. The former is therefore preferable.
This gives the following lowerbounds (n+y+1)*x
for the field area:
n area
60 71*6=426
111 149*3=447
230 254*10=2540
400 421*20=8240
480 505*20=10100
This suggests the following algorithm:
Find the value (n+y+1) which shall be the field height
Iterate from the squarest rectangle to the longest one
While there is space in the field height, draw each rectangle, one below the other, lined up on the left border.
When there is no more space in the field height, draw the remaining rectangles, one beside the other, along the bottom border, taking care not to overlap any of the rectangles above.
(Expand the field rightwards in the rare cases where this is necessary.)
It is actually possible to get all the rectangles for the required test cases within the above mentioned lower bounds, with the exception of 60, which gives the following 71*8=568 output. This can be improved slightly to 60*9=540 by moving the two thinnest rectangles right one square and then up, but the saving is minimal, so it's probably not worth any extra code.
10
12
15
20
30
60
******
******
******
******
******
******
******
******
******
******
***** *
***** *
***** *
***** *
***** *
***** *
***** *
***** *
***** *
***** *
***** *
***** *
*
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
**** *
*
*** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
*** ** *
** *
** *
** *
** *
** *
** *
** *
** *
** *
** *
** *
This gives a total area of 21895.
Ungolfed code
f=->n{
a=(0..n*2).map{' '*40} #Fill an array with strings of 40 spaces
g=0 #Total height of all rectangles
m=n #Height of squarest rectangle (first guess is n)
(n**0.5).to_i.downto(1){|i|n%i<1&&(puts n/i #iterate through widths. Valid ones have n%i=0. Puts outputs heights for debugging.
m=[m,h=n/i].min #Calculate height of rectangle. On first calculation, m will be set to height of squarest rectangle.
g+=h+1 #Increment g
g<n+m+2? #if the rectangle will fit beneath the last one, against the left margin
(a[g-h-1,1]=(1..h).map{'*'*i+' '*40}) #fill the region of the array with stars
: #else
(x=(n+m-h..n+m).map{|j|r=a[j].rindex('* ');r ?r:-2}.max #find the first clear column
(n+m-h+1..n+m).each{|j|a[j][x+2]='*'*i} #and plot the rectangle along the bottom margin
)
)}
a} #return the array
puts f[gets.to_i]