1042 Gone Fishing[DP/贪心]:钓鱼问题+易错数据集

题目大意

题目链接
去钓鱼,有n条湖按顺序排列,每条湖初始鱼量为fi,钓一个时间片就减少di条鱼,最小减到0条鱼,每条湖间的行走时间为ti个时间片(一个时间片为5分钟),给定时间h小时(一个小时12个时间片),要求最大的钓鱼数。

临界/易错数据集

2
1
0 0
1 1
1
答案
60 0 
0

贪心+优先队列

首先通过题目我们不难发现,为了得到最优解,那么就不能把时间浪费在路上,也就是说不能走回头路。然后很容易可以发现,在每个时刻在不同的鱼塘钓到的鱼的数量是不同的,为了保证钓到最多的鱼,那么我们每次钓都要选当前可以钓到鱼数量最多的鱼塘,钓完之后就更新这个鱼塘的钓鱼数量,再进行下一轮的钓鱼。

那么现在就出现一个问题:如果要想按照上面的贪心方法,每次到可以钓到鱼的数量最多的鱼塘里去钓鱼,那么就很可能出现要在几个鱼塘之间来来去去,会把时间浪费在路上。

怎样解决这个问题呢?

我们可以先确定可以走到的最远的鱼塘i,然后把时间减去从鱼塘1走到鱼塘i的时间,在剩下的时间里一直钓鱼,可以假设钓鱼人可以瞬间移动,在鱼塘1到鱼塘i之间采用上面的贪心方法,就可以求到最远走到鱼塘i的最优解。

为什么可以假设钓鱼人可以瞬间移动呢?

因为钓鱼人的钓鱼的范围是从鱼塘1到鱼塘i,所以他花的最少的移动时间就是从鱼塘1到鱼塘i的时间,那么剩下来的时间就可以全部用来钓鱼,那么这时我们要考虑的并不是钓鱼的次序,而是钓了哪几次鱼,也就是说,我们只要知道每次钓的鱼是在哪里钓的就行了,并不要知道从鱼塘1出发之后的钓鱼过程,而上面提到的贪心算法,恰恰求的就是每次钓的鱼是在哪里钓的。

解决了这个问题,那么还有一个问题:在实现贪心算法的时候,如何每次快速找到目前钓鱼数量最多的鱼塘并且实时更新鱼塘的钓鱼数量呢?

常规的话,就是要全部扫一遍,找到最大值,然后更新就更为麻烦,不可取。这时,就想到一种很高效的求最值的数据结构——优先队列。我们可以用一个优先队列,来储存当前可以钓到的鱼塘钓鱼数量,只要维护一个大根堆,就可以很容易地实现得到最大值和更新。

我们最后来总结一下贪心+优先队列的方法,我们以5分钟为一个单位时间,穷举所有可以到达的最远鱼塘,每次都用总时间减去花在路上的时间,也就是从鱼塘1到目前最远鱼塘的距离,就得到钓鱼的时间,然后就用贪心来求到当前情况下的最优解,贪心时取最大值和更新用优先队列来实现,最后在所有的最优解中选取一个最大的就得到最终答案

#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define MAX 30
#define inf 1e10
#define ll int

ll f[MAX], d[MAX], t[MAX];
ll c[MAX], tc[MAX];

struct P {
	int first, second;
	P(int a = 0, int b = 0) { first = a, second = b; }
};

bool operator <(P p1, P p2) {//重载比较运算符 使得鱼量相等时 索引小的排在前面
	if (p1.first == p2.first)return p1.second > p2.second;
	else return p1.first < p2.first;
}

int main() {
	ll n, h, res = -1;
	while (scanf("%d", &n) && n) {
		scanf("%d", &h); h = h * 12; res = -1;
		for (int i = 1; i <= n; i++)scanf("%d", &f[i]);
		for (int i = 1; i <= n; i++)scanf("%d", &d[i]);
		for (int i = 2; i <= n; i++)scanf("%d", &t[i]);

		for (int i = 1; i <= n; i++) {//遍历所有的策略 从1-i池塘钓鱼
			for (int j = 0; j <= n; j++)tc[j] = 0;
			int th = h, tr = 0;//th记录时间 tr记录结果
			for (int j = 2; j <= i; j++)th -= t[j];//先将所有池塘中走动的时间减去
			priority_queue<P> q;
			for (int j = 1; j <= i; j++)q.push(P(f[j], j));

			while (th > 0 && q.top().first > 0) {
				int x = q.top().first, id = q.top().second; q.pop();
				tr += x; q.push(P(x - d[id], id)); th--; 
				tc[id]++;
			}

			tc[1] += th; //剩余的时间都给第一个池塘
			if (tr > res) {//res初始值一定要是-1 才能处理结果为0的情况
				res = tr;
				for (int i = 1; i <= n; i++)c[i] = tc[i];
			}
		}
		for (int i = 1; i < n; i++)printf("%d, ", c[i] * 5);
		printf("%d\n", c[n] * 5);
		printf("Number of fish expected: %d\n\n", res);
	}
}

DP

根据题目中的描述:所有数据都是以5分钟为单位的,所以我们也把5分钟作为一个单位, 这样可以方便计算

定义dp[i][j]为前i个池塘,总共用时j分钟能钓到最多的鱼的数量

则dp[i][j]的多少取决于再最后一个池塘(第i个池塘)所花的时间

设在最后一个池塘钓了k分钟
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − t [ i ] − k ] + t m p ) dp[i][j]=max(dp[i-1][j-t[i]-k]+tmp) dp[i][j]=max(dp[i1][jt[i]k]+tmp)
tmp是钓k分钟的收获,t[i]是转移到第i个池塘需要的时间(是输入的元素)


PS:关于k的取值范围

  • 在前i个池塘花的时间为j,所以 t [ i ] + k ≤ j t[i]+k\leq j t[i]+kj
  • 下一个阶段如果钓鱼的数目是负数就不需要继续下去了 ( k − 1 ) d [ i ] < f [ i ] (k-1)d[i]<f[i] (k1)d[i]<f[i]

关于j的取值范围:
我们将输入的t[i]数组求一个前缀和,sum[i],那么sum[i]就是从1开始转移到i总用时,那么对于某个i,他的j总是从sum[i]开始的。这个也蛮重要的,我从t[i]开始就wrong,这样就对了。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值