火车运煤问题(驴运萝卜问题)
题目是这样的:A B两地距离1000km,A地有3000吨煤。A地有一辆火车,单次最大运载量为1000吨,每行驶 1km要消耗 1吨煤。现在要把这3000吨煤从A运输到B,求运到B最多还有多少吨煤?
对于这个问题,我想到了创建一个模型,根据题目要求得出初始状态、终极状态和中间条件,这里用一张图表示。
初始状态:A=3000, x = y = 0, B = 0
结束状态:A= 0, y - x = 1000, B + x + y = 3000
这里 火车单次最大负载 maxLoad = 1000, 煤总量 为 3000, 因此将路程 3000/1000 = 3 比较合适(这里不做证明)
看图分析可得,对于每段路程,往次数总是比返次数多1 ,所以可以推出 a[0] + a[1] + a[2] = 1000
所有条件均已在途中列出。 如果不能理解上图,可以考虑一种实际情况 a[0] = a[1] = 250, a[2] = 500。
下一个问题,就是如何通过编程来找出B的最大值。由于 a[0] + a[1] + a[2] = 1000,因此,可以用遍历的方法实现。
代码如下
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * out;
int total = 3000;
int a[] = {1, 1, 1}; // 记录每段 长度, 总长度为 1000 km
int t[] = {0, 0, 0}; // 记录每段路 返回经过的次数
int x = 0, xb = 0;
int xy = 0, xyb = 0; // x + y
int remain, remainb;
int i, j;
int max = 0;
// 创建一个文件,用来记录结果
if((out=fopen("res.txt", "a++"))==NULL){
fprintf(stdout, "Cannot open \"res.txt\" file\n");
exit(1);
}
// invariant: a[0] + a[1] + a[2] = 1000
// invariant: 剩下的煤量超过剩下的距离
for(i = 1; i <500; i++){
a[0] = i; // 第一段路程 0001
t[0] = 2;
xb = a[0] * t[0];
xyb = a[0] * ( 2 * t[0] + 1);
remainb = total - xyb; // 记录 x, xy, remain 以便于在内层循环起始处恢复
for(j = 1; j < 500; j++){
x = xb, xy = xyb, remain = remainb; // 每次都重新对 x, xy, remain赋值
a[1] = j; // 第二段路程 0002
t[1] = remain % 1000 == 0? (remain/1000 - 1):(remain/1000);
x += a[1] * t[1];
xy += a[1] * ( 2 * t[1] + 1);
if(x>=1000) continue; // x + y < 3000, 所以2 * x < 2000, 即 x < 1000
if(xy>=3000) continue; // x + y < 3000
remain = total - xy;
a[2] = 1000 - i - j; // 第三段路程 0003
if(a[2] > remain) continue;
t[2] = remain % 1000 == 0? (remain/1000 - 1):(remain/1000);
x += a[2] * t[2];
xy += a[2] * ( 2 * t[2] + 1);
if(x>=1000) continue; // x + y < 3000, 所以2 * x < 2000, 即 x < 1000
if(xy>=3000) continue; // x + y < 3000
remain = total - xy;
// 将 结果打印到 文件中,便于查看
fprintf(out, "i = %d, j = %d, x = %d, xy = %d, r = %d\n", i, j, x, xy, r);
if(remain > max) max = remain;
} // end of innner for loop j
} // end of outer for (i
printf("max = %d\n", max);
if(fclose(out)!=0){
fprintf(stdout, "close failed");
}
getchar();
return 0;
#include <stdlib.h>
int main(){
FILE * out;
int total = 3000;
int a[] = {1, 1, 1}; // 记录每段 长度, 总长度为 1000 km
int t[] = {0, 0, 0}; // 记录每段路 返回经过的次数
int x = 0, xb = 0;
int xy = 0, xyb = 0; // x + y
int remain, remainb;
int i, j;
int max = 0;
// 创建一个文件,用来记录结果
if((out=fopen("res.txt", "a++"))==NULL){
fprintf(stdout, "Cannot open \"res.txt\" file\n");
exit(1);
}
// invariant: a[0] + a[1] + a[2] = 1000
// invariant: 剩下的煤量超过剩下的距离
for(i = 1; i <500; i++){
a[0] = i; // 第一段路程 0001
t[0] = 2;
xb = a[0] * t[0];
xyb = a[0] * ( 2 * t[0] + 1);
remainb = total - xyb; // 记录 x, xy, remain 以便于在内层循环起始处恢复
for(j = 1; j < 500; j++){
x = xb, xy = xyb, remain = remainb; // 每次都重新对 x, xy, remain赋值
a[1] = j; // 第二段路程 0002
t[1] = remain % 1000 == 0? (remain/1000 - 1):(remain/1000);
x += a[1] * t[1];
xy += a[1] * ( 2 * t[1] + 1);
if(x>=1000) continue; // x + y < 3000, 所以2 * x < 2000, 即 x < 1000
if(xy>=3000) continue; // x + y < 3000
remain = total - xy;
a[2] = 1000 - i - j; // 第三段路程 0003
if(a[2] > remain) continue;
t[2] = remain % 1000 == 0? (remain/1000 - 1):(remain/1000);
x += a[2] * t[2];
xy += a[2] * ( 2 * t[2] + 1);
if(x>=1000) continue; // x + y < 3000, 所以2 * x < 2000, 即 x < 1000
if(xy>=3000) continue; // x + y < 3000
remain = total - xy;
// 将 结果打印到 文件中,便于查看
fprintf(out, "i = %d, j = %d, x = %d, xy = %d, r = %d\n", i, j, x, xy, r);
if(remain > max) max = remain;
} // end of innner for loop j
} // end of outer for (i
printf("max = %d\n", max);
if(fclose(out)!=0){
fprintf(stdout, "close failed");
}
getchar();
return 0;
}
之所以去 a[0] , a[1] 范围 是 1-500, 是因为除最后一段路不需要返回以外,其它都需要。需要返回,同时最大载货量为 1000,所以前进距离最多为 500。否则火车就白跑了。
如果 煤的总量扩展到 3000 * N, 使用循环不靠谱了,不过应该可以用回溯法来实现。
这种方法的另一个漏洞是,只能遍历整数,而数学上的最优解,是一个小数。这一点我还无法克服。