我正在尝试解决此问题:
给定一个a×b矩形,您的任务是将其切成正方形。每次移动时,您都可以选择一个矩形并将其切成两个矩形,以使所有边长均保持整数。最少可以移动多少次?
我的逻辑是,最少的切割数意味着最少的平方数;我不知道这是正确的方法。
我看到哪一侧较小,现在我知道我需要切割削减的bigSide / SmallSide以具有smallSide边的正方形,然后剩下SmallSide和bigSide%smallSide。然后我继续直到任一边都为0或两者相等。
#include <iostream>
int main() {
int a, b; std::cin >> a >> b; // sides of the rectangle
int res = 0;
while (a != 0 && b != 0) {
if (a > b) {
if (a % b == 0)
res += a / b - 1;
else
res += a / b;
a = a % b;
} else if (b > a) {
if (b % a == 0)
res += b / a - 1;
else
res += b / a;
b = b % a;
} else {
break;
}
}
std::cout << res;
return 0;
}
当输入为404 288
时,我的代码给出了18
,但正确的答案实际上是10
。
我在做什么错了?
答案 0 :(得分:4)
在我看来,问题很明显地定义了每个动作,即沿着整数线将一个矩形切割成两个矩形,然后要求这种切割的最小数量。如您所见,此问题具有明显的递归性质。将矩形切成两部分后,可以递归并以最少的移动将它们切成正方形,然后对答案求和。问题在于递归可能导致指数时间复杂度,这直接导致我们直接dynamic programming。您必须使用memoization才能有效地解决它(最坏的情况是ngOnChanges() {
setTimeout(() => this.input.nativeElement.select());
}
),这是我建议做的:
O(a*b*(a+b))
foor循环上升到#include <iostream>
#include <vector>
using std::vector;
int min_cuts(int a, int b, vector<vector<int> > &mem) {
int min = mem[a][b];
// if already computed, just return the value
if (min > 0)
return min;
// if one side is divisible by the other,
// store min-cuts in 'min'
if (a%b==0)
min= a/b-1;
else if (b%a==0)
min= b/a -1;
// if there's no obvious solution, recurse
else {
// recurse on hight
for (int i=1; i<a/2; i++) {
int m = min_cuts(i,b, mem);
int n = min_cuts(a-i, b, mem);
if (min<0 or m+n+1<min)
min = m + n + 1;
}
// recurse on width
for (int j=1; j<b/2; j++) {
int m = min_cuts(a,j, mem);
int n = min_cuts(a, b-j, mem);
if (min<0 or m+n+1<min)
min = m + n + 1;
}
}
mem[a][b] = min;
return min;
}
int main() {
int a, b; std::cin >> a >> b; // sides of the rectangle
// -1 means the problem is not solved yet,
vector<vector<int> > mem(a+1, vector<int>(b+1, -1));
int res = min_cuts(a,b,mem);
std::cout << res << std::endl;
return 0;
}
和a/2
的原因是裁切纸是对称的:如果沿垂直线b/2
裁切,则与沿线裁切相同i
(如果您垂直翻转纸张)。这是一个小小的优化技巧,可将复杂度总体降低a-i
。
另一个小技巧是,知道问题是如果对纸张进行转置,结果是相同的,只需4
,就可以将计算量减少一半。但是任何进一步的重大改进,例如贪婪算法,都将需要更多的思考(如果有的话)。