我最近遇到了以下编程挑战:
声明
考虑包含0和1的大小为NxN的2D方阵。您必须使用大小为1,2或3的正方形覆盖矩阵中的所有1。使用大小为1的正方形的覆盖成本为2,使用大小为2的正方形为4并且使用大小为3的正方形为7.目标是找到覆盖矩阵中所有1的最小覆盖成本。
约束
1 <= N <= 100
一般评论
示例
以下面的矩阵为例:
0 0 0 0 0 0 0 0
0 1 1 0 0 0 0 0
0 1 1 1 0 0 0 0
0 0 1 1 1 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 1 1 0
0 0 0 0 0 0 1 0
在上面的例子中,最小覆盖成本是7x1 + 4x2 + 2x1 = 17.另一种覆盖是可能的,最小覆盖成本为7x1 + 4x1 + 2x3 = 17.
我的方法
我试图通过以下方式解决问题:
这种方法很贪婪,并不是最优的。对于上面的例子,我的方法给出了答案7x1 + 4x2 + 2x2 = 19这不是最佳的。
有关如何解决此问题的任何指针或对可用于解决此问题的已知问题的引用都表示赞赏。感谢。
更新
从@bvdb回答中获得提示,我根据他们所覆盖的1的数量更新了选择覆盖范围的方法。但是,该方法仍然不是最优的。考虑一下我们有以下安排的情景:
1 0 1
0 0 0
1 0 1
这种安排将使用4个1号尺寸的正方形覆盖,而它们必须使用1平方的3号覆盖。一般来说,3x3区域中的5个1必须使用不同的策略覆盖,具体取决于它们在该区域的传播方式。我可以针对所有类型的案例对其进行硬编码,但我正在寻找一种优雅的解决方案,如果存在的话。
答案 0 :(得分:1)
您的问题是典型的Packing problem。
首先适应最大的盒子的方法非常有意义。
使您的算法更好的一种简单方法是优先选择3x3正方形并使用最大的强度。
<强> 实施例 强>
但是如果你想添加重叠,那么它会变得更加棘手。我相信你可以用数学方法解决这个问题。但是,当逻辑变得棘手时,总会想到Monte Carlo方法:
蒙特卡罗方法(或蒙特卡罗实验)是一类广泛的计算算法,依靠重复随机抽样来获得数值结果。它们通常用于物理和数学问题,并且在很难或不可能使用其他数学方法时最有用。
蒙特卡罗交换速度和随机性的编码逻辑:
mag
此代码应该简单但非常快。 然后运行100.000次,并保持前10名的分数。 获奖者最常使用哪些3x3广场? 将此信息用作&#34;起始位置&#34;。
现在,使用此起始位置从STEP2再次运行。 这意味着100.000次迭代不再需要关注3x3方块,它们会立即开始添加2x2方块。
PS:你所做的迭代次数(例如100.000)实际上是所需响应时间和所需精度的问题。您应该对此进行测试以找出可接受的内容。
答案 1 :(得分:1)
如果您正在寻找确定性方法。
我认为最好的办法是以最佳顺序对所有可能的模式进行排序。只有394种相关模式。无需对它们进行硬编码,您可以即时生成它们。
首先我们的定义(游戏规则)。每个广场都有尺寸和成本。
class Square
{
private int size;
private int cost;
Square(int pSize, int pCost)
{
size = pSize;
cost = pCost;
}
}
只有3种类型的方块。 squareOne
保留1x1矩阵的成本,squareTwo
为2x2,squareThree
为3x3矩阵。
Square squareOne = new Square(1, 2);
Square squareTwo = new Square(2, 4);
Square squareThree = new Square(3, 7);
List<Square> definitions = Arrays.asList(squareOne, squareTwo, squareThree);
我们将不得不存储每个模式的成本,点击次数,以及每次点击成本(效率)。所以这是我用来存储它的类。请注意,此类包含的方法有助于执行排序以及转换为boolean
的矩阵(1/0值)。
class ValuedPattern implements Comparable<ValuedPattern>
{
private long pattern;
private int size;
private int cost;
private double costPerHit;
private int hits;
ValuedPattern(long pPattern, int pSize, int pCost)
{
pattern = pPattern;
cost = pCost;
size = pSize;
// calculate the efficiency
int highCount = 0;
BitSet set = BitSet.valueOf(new long[]{pattern});
for (int i = 0; i < set.size(); i++)
{
if (set.get(i)) highCount++;
}
hits = highCount;
costPerHit = (double) cost / (double) hits;
}
public boolean[][] toArray()
{
boolean[][] patternMatrix = new boolean[size][size];
BitSet set = BitSet.valueOf(new long[]{pattern});
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
patternMatrix[i][j] = set.get(i * size + j);
}
}
return patternMatrix;
}
/**
* Sort by efficiency
* Next prefer big matrixes instead of small ones.
*/
@Override
public int compareTo(ValuedPattern p)
{
if (p == null) return 1;
if (costPerHit < p.costPerHit) return -1;
if (costPerHit > p.costPerHit) return 1;
if (hits > p.hits) return -1;
if (hits < p.hits) return 1;
if (size > p.size) return -1;
if (size < p.size) return 1;
return Long.compare(pattern, p.pattern);
}
@Override
public boolean equals(Object obj)
{
if (! (obj instanceof ValuedPattern)) return false;
return (((ValuedPattern) obj).pattern == pattern) &&
(((ValuedPattern) obj).size == size);
}
}
接下来,我们将在排序集合中存储所有可能的模式(即TreeSet
使用对象的compareTo
方法自动对其内容进行排序。
由于您的模式只是0和1的值,您可以将它们视为数值(long
是一个64位整数,这已经足够了),可以在以后转换为一个boolean
矩阵。模式的大小与该数值的位数相同。或者换句话说,有2 ^ x个可能的值,x是模式中的单元格数。
// create a giant list of all possible patterns :)
Collection<ValuedPattern> valuedPatternSet = new TreeSet<ValuedPattern>();
for (Square square : definitions)
{
int size = square.size;
int bits = size * size;
long maxValue = (long) Math.pow(2, bits);
for (long i = 1; i < maxValue; i++)
{
ValuedPattern valuedPattern = new ValuedPattern(i, size, square.cost);
// filter patterns with a rediculous high cost per hit.
if (valuedPattern.costPerHit > squareOne.cost) continue;
// and store the result for later
valuedPatternSet.add(valuedPattern);
}
}
在编写列表之后,模式已经根据效率排序。 现在您可以应用已有的逻辑。
// use the list in that order
for (ValuedPattern valuedPattern : valuedPatternSet)
{
boolean[][] matrix = valuedPattern.toArray();
System.out.println("pattern" + Arrays.deepToString(matrix) + " has cost/hit: " + valuedPattern.costPerHit);
// todo : do your thing :)
}
上面的演示代码以其效率输出所有模式。 请注意,较小的图案有时比较大图案的效率更高。
Pattern [[true, true, true], [true, true, true], [true, true, true]] has cost/hit: 0.7777777777777778
Pattern [[true, true, true], [true, true, true], [true, true, false]] has cost/hit: 0.875
Pattern [[true, true, true], [true, true, true], [true, false, true]] has cost/hit: 0.875
Pattern [[true, true, true], [true, true, true], [false, true, true]] has cost/hit: 0.875
...
整个过程只需几毫秒。
修改强> 我添加了一些代码,我不打算放在这里(但不要犹豫,然后我会通过电子邮件发给你)。但我只想展示它提出的结果:
<强> EDIT2:强> 我很遗憾地告诉你,你对我的解决方案提出质疑是正确的。事实证明,我的解决方案失败了:
0 0 0 0 0 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 0 0 0 0 0
我的解决方案是贪婪的,因为它立即尝试应用最有效的模式:
1 1 1
1 1 1
1 1 1
接下来只剩下以下内容:
0 0 0 0 0 0
0 _ _ _ 1 0
0 _ _ _ 1 0
0 _ _ _ 1 0
0 1 1 1 1 0
0 0 0 0 0 0
接下来,它将使用三个2x2方块来覆盖遗骸。 所以总成本= 7 + 3 * 4 = 19
最好的方法当然是使用四个2x2方格。 总成本为4 * 4 = 16
结论:因此,即使第一个3x3非常有效,下一个2x2模式的效率也会降低。现在你知道了这个异常,你可以将它添加到模式列表中。例如。尺寸为4的正方形的成本为16.然而,这并不能解决它,3x3仍然会有较低的成本/命中率,并且总是被认为是第一个。 所以,我的解决方案已被破解。