【问题描述】
约翰是个垂钓谜,星期天他决定外出钓鱼h小时(1≤h≤16),约翰家附近共有n个池塘(2≤n≤25),这些池塘分布在一条直线上,约翰将这些池塘按离家的距离由近到远编上号,依次为L1,L2,…,Ln,约翰家门外就是第一个池塘,所以他到第一个池塘是不用花时间的。
约翰可以任选若干个池塘由近到远地垂钓,并且在每个池塘他都可以呆上任意长的时间,但呆的时间必须为5分钟的倍数(即5分钟为一个单位时间),已知从池塘Li到池塘Li+1要化去约翰ti个单位时间。每个池塘的上鱼率预先也是已知的,池塘Li在第一个单位时间内能钓到的鱼为Fi(0≤Fi≤100),并且每当他在某一个鱼塘呆上一个单位时间后,该鱼塘单位时间内能钓到的鱼将减少一个常数di(0≤di≤100)。
现在请你编一个程序计算约翰最多能钓到多少鱼。
【输入格式】
第一行为一个整数n,第二行为一个整数h,第三行为n个用空格隔开的整数,表示Fi(i=1,2,…,n),第四行为n个用空格隔开的整数,表示di(i=1,2,…,n),第五行为n-1个用空格隔开的整数,表示ti(i=1,2,…,n-1)
【输出格式】
一个整数,表示约翰最多能钓到鱼的数量。
【输入样例】
2
1
10 1
2 5
2
- 1
- 2
- 3
- 4
- 5
【输出样例】
31
- 1
【数据范围】
2≤n≤25 , 1≤h≤16 , 0≤ti≤100 , 0≤Fi≤100 , 0≤di≤100
解法一: DP
DP的状态和状态转移:
dp[i][j]表示第j个五分钟时John位于池塘 i 的情况最多可以钓到多少鱼。
dp[i][j] = max(dp[i][j], dp[i - 1][j - t[i] - k]);
实际处理时,对于所有可行的k: dp[i + 1][j + k + t[i]] = max(dp[i + 1][j + k + t[i]], dp[i][j] + sum), 其中sum是在i+1处钓鱼k个5分钟所钓到的鱼。(注意细节)!
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 36;
int dp[MAXN][2400], f[MAXN], d[MAXN], n, t[MAXN];
int main() {
int i, j, SumT, res[MAXN][2400];//res用来保存路径
while (scanf("%d", &n) && n) {
scanf("%d", &SumT);
SumT *= 12;
for(i = 1; i <= n; i++)
scanf("%d", &f[i]);
for(i = 1; i <= n; i++)
scanf("%d", &d[i]);
for(i = 1; i < n; i++)
scanf("%d", &t[i]);
int nowT = SumT;
memset(dp, 0, sizeof(dp));
memset(res, 0, sizeof(res));
for(i = 1; i <= n; i++) { //枚举要钓鱼的前i个池塘
for (j = 1; j <= nowT; j++) {//枚举时间
dp[i][j] = dp[i-1][j];
int p = 1;
int tmp = f[i];
while (dp[i-1][j-p]+tmp > dp[i][j] && j-p >= 0) {
dp[i][j] = dp[i-1][j-p] + tmp;
res[i][j] = p;
tmp += (f[i]-p*d[i]) > 0 ? (f[i]-p*d[i]):0;
p++;
}
}
if (i != n) nowT -= t[i];//nowT记得更新
}
i = n;
j = nowT;
int ii = n;
int maxx = dp[ii][nowT];
while (ii != 1) {//找出钓鱼最多的那种方案
nowT += t[--ii];
if (dp[ii][nowT] >= maxx) {//这里是>=,要尽量钓前面的鱼。
maxx = dp[ii][nowT];
i = ii;
j = nowT;
}
}
int result[MAXN] = {0};//注意置零
while (i != 0) {//搞出result,就是每个池塘钓的鱼
result[i] = res[i][j]*5;
SumT -= res[i][j];
SumT -= t[i-1];
j -= res[i][j];
i--;
}
result[1] += SumT*5;//这里还剩的时间说明钓不到鱼了,全部加到第一个池塘
for(i = 1; i <= n; i++)
if (i != n)
printf("%d, ", result[i]);
else printf("%d\n", result[i]);
printf("Number of fish expected: %d\n\n", maxx);
}
return 0;
}
方法二: 枚举+贪心
实际上枚举+贪心的方法更容易,数据量不大,所以速度更快。
我们可以把总时间分为两个部分:在路上的时间和在钓鱼的时间。由于路是单行的,所以在路上的时间取决于走的最远距离,即到达的池塘的最大编号。 剩余的时间用于钓鱼。我们可以把移动+钓鱼的混合过程拆分为移动的过程和钓鱼的过程,即指定一个池塘为终点,移动过程即从起点到终点的过程,钓鱼的过程就是从起点到终点的各个池塘中选择池塘钓鱼,因为移动过程所需的时间已经在前面考虑过了,这个时候我们就可以认为需要移动的时候可以直接瞬间到达。然后,选择到哪些池塘钓鱼的策略采用贪心的方法,每个钓鱼的5分钟都选择期望最大的那一个池塘,每在选择一个池塘钓鱼5分钟,减少相应池塘的期望,增加计划中在该池塘钓鱼的时间,然后继续选择期望最大的池塘,直到钓鱼的时间不够,或者池塘里没有鱼了。如果池塘没有鱼了,还有时间的话,把多余的时间分配给第一个池塘。
以上解释的是贪心策略,枚举所有池塘作为终点时贪心结果,再选择最优的方案即可。
由于数据规模不大,每一步贪心的时候都直接遍历数组实现了,有人提到了用堆或优先队列来做,也许能快一些。
代码就不发了,注意细节就好了!