A 地有一堆香蕉,共 w 只。
一只猴子要把香蕉从 A 地运输到 B 地。
两地相距 s 里。
猴子每次最多只能背 c 只香蕉。
无论背多少香蕉(甚至不背),猴子每走 1 里路,会吃掉 1 只香蕉。
当然,猴子在完成运输任务后,并不需要返回 A 地。
猴子只在整数里程点往返。
求: 在使用最佳策略时,最多能运多少香蕉到 B 地。
比如: w=30, s=3, c=10,
则,最多可运去 17 只香蕉。
考虑一搬性,w s c 都可能很大,比如:
w=987654321, s=32345678, c=10000000
(这只是个数学问题,不要说猴子背不动什么的…)
输入数据保证都是大于 0 的。
首先,如果 c >= w,所有香蕉一次就可运完,则为简单情况。
更多的情况是需要往返运输。则要考虑怎样才节省。
显然,此时,猴子把香蕉一里一里地运输总是最优的。
因此,可得最笨的解法。
使用这种解法要注意一个陷阱,那就是零头的处理问题。
在此基础下,可以分析出哪些步骤可以合并,则得更优解法。
这个问题有 3 个变量,在考虑边界条件的时候要小心一些。
这 3 个变量的地位不一样,在给定一个具体问题时,
c 变成常量,而 w, s 随着求解过程不断变化。
先来个笨的,较可靠的:
//problem005
public class A
{
// 保证输入数据大于0
static int f(int w, int s, int c){
if(c < 2) return 0;
if(c == 2){
if(s > 1) return 0;
if(w < 2) return 0;
return 1;
}
if(w <= s) return 0;
if(c > w) return w-s;
while(s > 0){
if(w < s) {
w = 0;
break;
}
if(w%c <= 2) w -= w%c;
w -= (w-1) / c * 2 + 1;
s--;
}
return w;
}
static void test(int w, int s, int c){
System.out.println(String.format("w=%d s=%d c=%d --> %d", w, s, c, f(w,s,c)));
}
public static void main(String[] args){
test(30,3,10);
test(30,6,10);
test(3000,1000,1000);
test(505,497,1000);
test(987654321,32345678,10000000);
test(30,1,2);
test(30,10,2);
test(30,40,10);
test(30,28,100);
}
}
进行必要的合并后,得到的更优的解法:
//problem005
public class B
{
static int f(int w, int s, int c){
if(c < 2) return 0;
if(c == 2){
if(s > 1) return 0;
if(w < 2) return 0;
return 1;
}
if(w <= s) return 0;
if(c > w) return w-s;
while(s>0){
if(w <= s) return 0;
if(w%c <= 2) w -= w%c;
int n = (w-1)/c;
int u = w - n * c;
int dw = n * 2 + 1;
int m = (u-2+dw-1)/dw;
if(s<m) m = s;
w -= m * dw;
s -= m;
}
return w;
}
static void test(int w, int s, int c){
System.out.println(String.format("w=%d s=%d c=%d --> %d", w, s, c, f(w,s,c)));
}
public static void main(String[] args){
test(30,3,10);
test(30,6,10);
test(3000,1000,1000);
test(505,497,1000);
test(987654321,32345678,10000000);
test(30,1,2);
test(30,10,2);
test(30,40,10);
test(30,28,100);
test(100,50,20);
}
}
这个小题目更多地是练习数学,练习整数与整除,余数等的性质。
用小的数据跟随程序上的公式逐步跟踪一下。
如果还是不能理解,可参考笔者在“千聊”上的同样题目的详解。
或者,手机扫下图加关注。