★问题描述:
约翰正在钓鱼。他有h个小时可用(1 <= h <= 16),并且该区域有n个湖泊(2 <= n <= 25),沿着一条单向公路可达。约翰从1号湖开始,但他可以在任何他想要的湖上完成。他只能从一个湖到另一个湖,但他不必在任何湖边停下来,除非他希望。对于每个i = 1,...,n-1,从湖i到湖i + 1所需的5分钟间隔的数量被表示为ti(0 <ti≤192)。例如,t3 = 4意味着从3号湖到4号湖需要20分钟的时间。为了帮助计划他的钓鱼之旅,John收集了关于湖泊的一些信息。对于每个湖我都知道在最初的5分钟内捕获的鱼的数量表示为fi(fi> = 0)。每5分钟的捕鱼次数减少预计在接下来的5分钟间隔内以不变率di(di> = 0)捕获的鱼的数量。如果预计在一段时间内捕获的鱼的数量小于或等于di,则在下一个时间段内湖中将不再有鱼。为了简化规划,约翰假定没有其他人会在湖边钓鱼,以影响他期望捕获的鱼的数量。
写一个程序,以帮助约翰计划他的钓鱼之旅,以最大限度地增加预计捕获的鱼的数量。在每个湖泊花费的分钟数必须是5的倍数。
★算法设计:
给定n个湖泊、可用h小时,n个湖泊的第一个时间片可捕获鱼的数目、n个湖泊每次减少的数目以及从第i个湖泊走到第i+1个湖泊所需的时间。根据上述数据计算出一个最优方案。
★数据输入:
您将在输入中获得一些案例。每种情况都以包含n的行开始。这后面跟着一个包含h的行。接下来,有一行n个整数指定fi(1 <= i <= n),然后是一行n个整数di(1 <= i <= n),最后是一行n - 1个整数ti 1 <= i <= n-1)。输入由n = 0的情况终止。
★数据输出:
对于每个测试案例,打印每个湖泊花费的分钟数(以逗号分隔),以实现预计捕获的鱼数量最大的计划(即使超过80个字符,您应该在一行上打印整个计划)。接下来是一条包含预期鱼类数量的线。
如果存在多个计划,则选择在湖1尽可能长时间花费的计划,即使预计在某些间隔内不会捕获鱼。如果还有一条路径,选择在湖边2尽可能长的那一条,等等。在个案之间插入一个空行。
★结果输出:
输入文件示例 输出文件示例
input.txt output.txt
2 45, 5
1 The total number of fish is:31
10 1
2 5
2
4 240, 0, 0, 0
4 The total number of fish is:480
10 15 20 17
0 3 4 3
1 2 3
二、程序功能简介
每个湖泊拥有以下参数:1.走到下一个湖泊所需的时间;2.第一个时间片所能钓到的鱼的数目;3.每过一个时间片所减少的鱼的数目。同时输入数据:湖泊的数量n、可用的时间h(单位:小时)。通过上述数据来得出一个最优的方案使得钓到的鱼的数量最多。
三、主体内容
3.1设计分析
这道题目首先题目就很长,参数也很多,所以第一眼看上去会感觉很懵比,但只要读多几遍,就可以大概捋清问题了。题目大概可以理解为有n个湖泊,小明可以在每个湖泊钓鱼,钓鱼时第一个时间片在输入时已经确定,而又因为输入时确定了每个湖泊的减少速率,所以假设有3个湖泊,问题可转化为下图1:

小明要做的就是从湖泊1开始走,直到消耗完可用时间,在可用时间内所能钓到的鱼的数目最多。而最后停留的湖泊任意,但不能回头。
由图1所示,我们可以算出当小明分别停留在湖泊1、湖泊2、湖泊3中的局部最优方案,所以我们就可以在三个最优方案中再选取最优方案,则得出最优方案。
而在选取特定湖泊时,如何筛选出局部最优方案?我们从图1来讨论这个问题,假设可用纯钓鱼时间片为4个,那么停留在湖泊1时,只能有一种选择,结果为50;当停留在湖泊2时,就有多种选择,在湖泊1可以用0、1、2、3、4个时间片,那么湖泊2对应的可用时间片为4、3、2、1、0。在这5种情况下,可简单地得出最优解为湖泊1用1个时间片,湖泊2用3个时间片,鱼的总数为80.同理,加入停留在湖泊3,那么最优方案为湖泊1用1个时间片,湖泊2用2个时间片,湖泊3用1个时间片,鱼的总数为80。通过上述三个局部最优方案,发现停留在湖泊2和停留在湖泊3的总数均为80,那么选取湖泊3的最优解。
经过上述的问题假设与讨论,我们发现其实题目可转化为:假设有4个时间片,停留在湖泊3,那么选取湖泊1到湖泊3的时间片单位内所能钓到的鱼的数目最多的前4个时间片。再将问题转化,假设有4个时间片,停留在湖泊3。那么小明走的路线就是1 --> 2 --> 3,当我选取每个湖泊的第一个时间片后,假如还有钓鱼时间片,那么就选取剩下的时间片。当我选取湖泊1的第二个时间片,就相当于在湖泊1的第一个时间片和湖泊2的第一个时间片中间插入一个时间片,不影响整体路线安排。因此,就可以将问题转换为假设可用时间片为h,选择湖泊k为停留湖,那么就在湖泊1到湖泊k中的所有时间片内选取前h个时间片即可得到最优解
3.2程序结构


3.3各模块的功能及程序说明
模块1:从文件中获取数据
/**********************************/
ifstream infile("input.txt");
infile>>n;
infile>>hours;
for(i=1; i<=n; i++)
infile>>fish[i];
for(i=1; i<=n; i++)
infile>>lessen[i];
for(i=1; i<=n-1; i++)
infile>>foot[i+1];
程序说明:读入文件"input.txt"需与源代码文件在同一个文件夹目录下
/************************************/
模块2: 将函结果写入输出文件
/************************************/
ofstream outfile("output.txt");
for(i=1; i<=n-1; i++)
outfile<<rePath[i]*5<<", ";
outfile<<rePath[i]*5<<endl;
outfile<<"The total number of fish is:"<<result;
程序说明:写入文件"output.txt"需与源代码文件在同一个文件夹目录下
/************************************/
模块3:函数体调用
/************************************/
int goneFish(int n, int hours)
程序说明:goneFish()函数计算结果并将结果以函数返回值形式返回到主函数
/************************************/
模块4: 核心运算
/************************************/
for(i=1; i<=k; i++)
{
if(maxFish < tmpFish[i])
{
maxFish=tmpFish[i];
maxPosition=i;
}
}
程序说明:遍历从第一个湖泊到第k个湖泊中,找出所能钓到的鱼的数量最多的湖泊
/************************************/
模块5: 数据处理
/************************************/
path[1] +=tmpHours;
if(res<sum)
{
res=sum;
for(j=1; j<=n; j++)
rePath[j]=path[j];
}
程序说明:将每个湖泊的最优解进行对比,取最优解并存储起来,方便后续输出
/************************************/
3.4源程序
/**********************************************************
题目: 钓鱼问题
来源: 百练1042
链接: http://bailian.openjudge.cn/practice/1042/
代码参考链接: https://blog.csdn.net/yew1eb/article/details/11785973
**********************************************************/
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
int *fish; //记录每个湖泊第一个时间片的鱼的数量
int *lessen; //记录每个湖泊每次减少的数量
int *foot; //记录从第i个湖泊走到第i+1个湖泊所需的时间
int *tmpFish; //中间值,记录每个湖泊特定时间片的鱼的数量
int *path; //中间值,记录每个湖泊所拥有的时间片数量
int *rePath; //结果值,记录每个湖泊所拥有的时间片数量
//goneFish()函数: 计算结果
int goneFish(int n, int hours)
{
//1.定义相关变量
int i, j, k, res=-1000; //1.1 "res=-1000" 解释: res作为函数返回值,记录鱼的总数,使其一开始时的数尽量小
//2.核心代码: 假设每个湖泊都是最后的停靠点,所以共有n种方案,每种方案选最优,再从n种方案中选最优,即为结果值
for(k=1; k<=n; ++k)
{
//3.当可使用时间大于从第一个湖泊走到第k个湖泊所花费的时间时
if(hours > foot[k])
{
//1.定义相关变量
int tmpHours=hours-foot[k]; //1.1 减去步行时间,计算出可利用的纯钓鱼时间
int sum=0; //1.2 中间值,记录每种方案的鱼的总数
//2.预处理数据
for(i=1; i<=n; i++) //2.1 初始化path[]数组
path[i]=0;
for(i=1; i<=n; i++) //2.2 用tmpFish[]数组将fish[]数组内容预存起来
tmpFish[i]=fish[i];
//3.用纯钓鱼时间计算结果
while(tmpHours > 0) //当拥有纯钓鱼时间时
{
//3.1 定义相关变量
int maxFish=0; //3.1.1 中间值,记录每种方案中每个湖泊中特定时间片所能钓到的最大数量的鱼
int maxPosition=0; //3.1.2 记录最多鱼的数量的湖泊编号
//3.2 核心运算
for(i=1; i<=k; i++) //3.2.1 遍历从第一个湖泊到第k个湖泊中,找出所能钓到的鱼的数量最多的湖泊
{
if(maxFish < tmpFish[i]) //3.2.2 当前湖泊的所能钓到的鱼的数目大于前面的所能钓到的鱼的最大值,则更新数据
{
maxFish=tmpFish[i]; //3.2.3 更新最大值
maxPosition=i; //3.2.4 更新湖泊的编号
}
}
//3.3 数据处理
if(!maxPosition) //3.3.1 当为第0个湖泊时,强制退出
break;
sum += maxFish; //3.3.2 将得到的鱼的数目更新到sum值
tmpFish[maxPosition] -=lessen[maxPosition]; //3.3.3 将第maxPosition个湖泊的鱼的最大数目更新,用于下一轮比较
path[maxPosition]++; //3.3.4 为第maxPosition个湖泊增加一个时间片数量
tmpHours--; //3.3.5 tmpHours-1 解释: 因为找到一个符合的方案,就减去一个时间片
}
//4.数据处理
path[1] +=tmpHours; //4.1 假如经过上述循环之后,若剩余时间为负数,则减少第一个湖泊的时间片数量
if(res<sum) //4.2 当总数比sum小,则更新总数res
{
res=sum; //4.3 更新总数ans
for(j=1; j<=n; j++) //4.4 将当前得到的每个湖泊的时间片记录下来,方便后续的输出
rePath[j]=path[j]; //4.5 将每个湖泊的时间片存进rePath[]数组里面
}
}
}
return res; //返回最优方案中鱼的总数
}
int main()
{
//1.定义相关变量并从文件里面读取数据,赋值给变量
int n, hours, i, result; //1.1 定义变量: n表示有n个湖泊,hours表示可用总时间,result将函数返回结果存起来
ifstream infile("input.txt"); //1.2 定义一个文件流,以输入方式打开文件"input.txt"
infile>>n; //1.3 从文件"input.txt"里面读取第一个数据作为n
infile>>hours; //1.4 从文件"input.txt"里面读取第二个数据作为hours
fish=new int[n+1]; //1.5 根据n给fish[]数组动态分配空间
lessen=new int[n+1]; //1.6 根据n给lessen[]数组动态分配空间
foot=new int[n]; //1.7 根据n给foot[]数组动态分配空间
tmpFish=new int[n+1]; //1.8 根据n给tmpFish[]数组动态分配空间
path=new int[n+1]; //1.9 根据n给path[]数组动态分配空间
rePath=new int[n+1]; //1.10 根据n给rePath[]数组动态分配空间
for(i=1; i<=n; i++) //1.11 从文件"input.txt"里面读取n个数据作为fish[]数组里面的值
infile>>fish[i];
for(i=1; i<=n; i++) //1.12 从文件"input.txt"里面读取n个数据作为lessen[]数组里面的值
infile>>lessen[i];
for(i=1; i<=n-1; i++) //1.13 从文件"input.txt"里面读取n个数据作为foot[]数组里面的值
infile>>foot[i+1]; //foot[i+1]解释: 因为到第一个湖泊的时间为0,所耗步数时间应该从第二湖泊开始算起
//2.数据预处理
hours *= 12; //2.1 将得到的小时数目转换成时间片数目
for(i=2, foot[1]=0; i<=n; i++) //2.2 将foot[]数组里面的值转换成: t[i]表示为从第一个湖泊走到第i个湖泊所需的时间
foot[i] +=foot[i-1]; //解释: 进行运算,通过不断"自身+前一个数"的方法来得到运算结果
//3.调用函数计算结果
result=goneFish(n, hours); //3.1 用变量result来接住函数返回值(即为所能钓到鱼的总数)
//4.输出结果
/***************************************************
//在小黑框里面输出结果
for(i=1; i<=n-1; i++)
cout<<rePath[i]*5<<",";
cout<<rePath[i]*5<<endl;
cout<<"The total number of fish is:"<<result<<endl;
***************************************************/
ofstream outfile("output.txt"); //4.1 定义一个输入文件流,以输入方式打开文件"input.txt"
for(i=1; i<=n-1; i++) //4.2 将结果按照规定格式写入文件"input.txt"
outfile<<rePath[i]*5<<", "; //4.3 输出格式控制
outfile<<rePath[i]*5<<endl; //4.4 因为最后一个数的后面没有逗号,所以以单独一行写入
outfile<<"The total number of fish is:"<<result; //4.5 将鱼的总数结果写入文件
//5.关闭文件流
infile.close(); //5.1 关闭输入文件流
outfile.close(); //5.2 关闭输出文件流
return 0;
}
3.5运行结果截图
测试数据 | 输入文件(input.txt) | 输出文件(output.txt) |
1 | 2 1 10 1 2 5 2 | 45,5 The total number of fish is:31 |
2 | 4 4 10 15 20 17 0 3 4 3 1 2 3 | 240,0, 0, 0 The total number of fish is:480 |
3 | 4 4 10 15 50 30 0 3 4 3 1 2 3 | 115,10, 50, 35 The total number of fish is:724 |



3.6设计体会
这个题目是属于我自己找的一道提高题,题目来源是百练P1042。而我是从mooc网站的视频里面看到了,感觉作为实训的一道提高题,挺合适的。这道题跟区间覆盖问题都是采用贪心法,区别也是挺大的,最直观的表现是题目的长度,区间覆盖问题的题目内容比较简单,但钓鱼问题的题目比较长,而且变量又多,所以很容易导致对钓鱼问题望而生畏。但真正理解钓鱼问题之后,会发现求解思路非常简单,就是求前k个最大值。这道题的代码是从网上找的,但没任何注释,所以只能自己慢慢理解并注释好,同时将代码重新按照自己的理解和编码习惯改动了一下。在改动的过程,对题目是越来越理解。所以在上述写题解的过程中,最后的转换是通过对题目的理解以及参考他人代码得出的个人理解思路。
从这个题目让我对贪心法有了一个更加深刻的理解,同时也是根据网上搜索到的资料以及对区间覆盖问题和钓鱼问题的对比,会发现贪心法的代码是存在一个如下图所示的代码框架:
四、附录
4.1程序流程图


4.2主要过程列表
如图4.1-1所示,在main()函数里面获取到n、k、fish[]、lessen[]、foot[]数组内容,然后调用函数goneFish()计算结果,将结果以函数返回值返回到main()函数,并写入到输出文件"output.txt"。其中,函数代码逻辑图,如图4.1-2所示,假设每个湖泊都作为最后的停留湖泊,那么即可得到n种局部最优解。当选定了湖泊k后,那么所消耗的步行时间也就确定了,所剩下的纯钓鱼时间即可用来分配各个湖泊的时间片,当编号为Position的湖泊为当前情况的单位时间片内能钓到最多的湖泊,记录下来,并在数组repath[]对应的湖泊的时间片数量+1,直到遍历完所有湖泊,退出循环,得到结果并返回到主函数中。
4.3程序中的主要变量、函数
int *fish; //记录每个湖泊第一个时间片的鱼的数量
int *lessen; //记录每个湖泊每次减少的数量
int *foot; //记录从第i个湖泊走到第i+1个湖泊所需的时间
int *tmpFish; //中间值,记录每个湖泊特定时间片的鱼的数量
int *path; //中间值,记录每个湖泊所拥有的时间片数量
int *rePath; //结果值,记录每个湖泊所拥有的时间片数量
int n; //n表示有n个湖泊
int hours; //hours表示可用总时间
int result; //result将函数返回结果存起来
int res; //res作为函数返回值,记录鱼的总数
ifstream infile("input.txt"); //定义一个文件流,以输入方式打开文件"input.txt"
ofstream outfile("output.txt"); //定义一个输入文件流,以输入方式打开文件"input.txt"
int goneFish(int n, int hours) //goneFish()函数: 计算结果,将结果以函数返回值形式返回到主函数