覆盖给定矩形区域所需的最小矩形

时间:2016-09-03 11:42:57

标签: algorithm math geometry

我有一个矩形的维度区域:n*m。我还有一个较小的维矩形:x*y。覆盖较大矩形的所有区域所需的最小矩形数量是多少?

没有必要打包较小的矩形。允许它们相互重叠,如果需要,可以跨越较大矩形的边界。唯一的要求是我们必须使用最少数量的x*y矩形。

另一件事是我们可以根据需要旋转较小的矩形(我的意思是90度旋转),以最小化数字。

n,m,x和y:都是自然数。 x,y不必是n,m的因子。

我无法在给定的时间内解决它,我也无法找到方法。我通过采用不同的n个案例开始,m可以被x,y整除。

更新

示例测试用例:

  • n * m = 3 * 3,x * y = 2 * 2。结果应为4
  • n * m = 5 * 6,x * y = 3 * 2。结果应为5
  • n * m = 68 * 68,x * y = 9 * 8。结果应为65

1 个答案:

答案 0 :(得分:3)

(更新:请参阅下面的新版本。)

我认为(但我目前没有证据证明)可以丢弃不规则的倾斜,找到最佳解决方案意味着找到切换方块的方向。

你从这样的基本网格开始:

tiling - basic grid

并且最佳解决方案将采用以下两种形式之一:

solution type 1 solution type 2

因此,对于每个点,您可以计算两个选项所需的切片数量:

all grid points

这是一个非常基本的实现。结果中的“水平”和“垂直”值是非旋转区域中的瓦片数量(图像中以粉红色表示)。

该算法可能会检查两次,并可以使用一些记忆来加快速度。

(测试显示您需要在切换x和y参数的情况下再次运行算法,并且检查两种类型的解决方案确实是必要的。)

function rectangleCover(n, m, x, y, rotated) {
    var width = Math.ceil(n / x), height = Math.ceil(m / y);
    var cover = {num: width * height, rot: !!rotated, h: width, v: height, type: 1};
    for (var i = 0; i <= width; i++) {
        for (var j = 0; j <= height; j++) {
            var rect = i * j;

            var top = simpleCover(n, m - y * j, y, x);
            var side = simpleCover(n - x * i, y * j, y, x);
            var total = rect + side + top;
            if (total < cover.num) {
                cover = {num: total, rot: !!rotated, h: i, v: j, type: 1};
            }
            var top = simpleCover(x * i, m - y * j, y, x);
            var side = simpleCover(n - x * i, m, y, x);
            var total = rect + side + top;
            if (total < cover.num) {
                cover = {num: total, rot: !!rotated, h: i, v: j, type: 2};
            }
        }
    }
    if (!rotated && n != m &&  x != y) {
        var c = rectangleCover(n, m, y, x, true);
        if (c.num < cover.num) cover = c;
    }
    return cover;

    function simpleCover(n, m, x, y) {
        return (n > 0 && m > 0) ? Math.ceil(n / x) * Math.ceil(m / y) : 0;
    }
}
document.write(JSON.stringify(rectangleCover(3, 3, 2, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(5, 6, 3, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(22, 18, 5, 3)) + "<br>");
document.write(JSON.stringify(rectangleCover(1000, 1000, 11, 17)));

这是反例Evgeny Kluev提供的:(68,68,9,8),它返回68,而只有65个矩形的解决方案,如图所示:

counter-example (68,68,9,8)

更新:改进算法

反例展示了算法概括的方法:从4个角落开始,尝试所有独特的方向组合,以及区域之间边界a,b,c和d的每个位置;如果在中间未覆盖矩形,请尝试两个方向来覆盖它:

rectangle covering from the corners

以下是这个想法的简单,未经优化的实现;它可能多次检查一些配置,11×17/1000×1000测试需要6.5秒,但它找到了反例和上一版本的其他测试的正确解决方案,因此逻辑似乎是合理的。

rotations

这是代码中使用的五个旋转和区域的编号。如果大矩形是正方形,则仅检查前3个旋转;如果小矩形是正方形,则仅检查第一次旋转。 X [i]和Y [i]是区域i中的矩形的大小,并且w [i]和h [i]是以矩形的数量表示的区域i的宽度和高度。

function rectangleCover(n, m, x, y) {
    var X = [[x,x,x,y],[x,x,y,y],[x,y,x,y],[x,y,y,x],[x,y,y,y]];
    var Y = [[y,y,y,x],[y,y,x,x],[y,x,y,x],[y,x,x,y],[y,x,x,x]];
    var rotations = x == y ? 1 : n == m ? 3 : 5;
    var minimum = Math.ceil((n * m) / (x * y));
    var cover = simpleCover(n, m, x, y);

    for (var r = 0; r < rotations; r++) {
        for (var w0 = 0; w0 <= Math.ceil(n / X[r][0]); w0++) {
            var w1 = Math.ceil((n - w0 * X[r][0]) / X[r][1]);
            if (w1 < 0) w1 = 0;
            for (var h0 = 0; h0 <= Math.ceil(m / Y[r][0]); h0++) {
                var h3 = Math.ceil((m - h0 * Y[r][0]) / Y[r][3]);
                if (h3 < 0) h3 = 0;
                for (var w2 = 0; w2 <= Math.ceil(n / X[r][2]); w2++) {
                    var w3 = Math.ceil((n - w2 * X[r][2]) / X[r][3]);
                    if (w3 < 0) w3 = 0;
                    for (var h2 = 0; h2 <= Math.ceil(m / Y[r][2]); h2++) {
                        var h1 = Math.ceil((m - h2 * Y[r][2]) / Y[r][1]);
                        if (h1 < 0) h1 = 0;
                        var total = w0 * h0 + w1 * h1 + w2 * h2 + w3 * h3;
                        var X4 = w3 * X[r][3] - w0 * X[r][0];
                        var Y4 = h0 * Y[r][0] - h1 * Y[r][1];
                        if (X4 * Y4 > 0) {
                            total += simpleCover(Math.abs(X4), Math.abs(Y4), x, y);
                        }
                        if (total == minimum) return minimum;
                        if (total < cover) cover = total;
                    }
                }
            }
        }
    }
    return cover;

    function simpleCover(n, m, x, y) {
        return Math.min(Math.ceil(n / x) * Math.ceil(m / y),
                        Math.ceil(n / y) * Math.ceil(m / x));
    }
}

document.write("(3, 3, 2, 2) &rarr; " + rectangleCover(3, 3, 2, 2) + "<br>");
document.write("(5, 6, 3, 2) &rarr; " + rectangleCover(5, 6, 3, 2) + "<br>");
document.write("(22, 18, 5, 3) &rarr; " + rectangleCover(22, 18, 5, 3) + "<br>");
document.write("(68, 68, 8, 9) &rarr; " + rectangleCover(68, 68, 8, 9) + "<br>");