鱼塘钓鱼问题

这题目给了三种做法,两种是动态规划做法,一种是贪心加多路,还有一种是堆写法,这可以促进我对动态规划的理解,所以在此贴出四种板子,并且给出解释
第一种做法,自下而上更新

 

鱼塘的下标从1到N;
·第一组两重循环表示的是初始化dp数组,dp[i][j]表示的是第i个池塘待j分钟可以获得的收益
·第二组两重循环进行状态转移:
··从倒数第二个池塘进行转移。这里是将一个池塘走到另一个池塘的时间加在了第一个池塘被分配的时间上,也就是说最后一个池塘是没有转移时间消耗的。(c[i]从1开始读取,到N-1停止)
··第二层循环终止条件是j>c[i],因为对消耗转移时间的池塘来说,j<=c[i]的话收益一定是0,不会再有状态转移,一定要有给下一个池塘分配的时间
··先存下在本池塘待j分钟所得的收益,再用一层循环去枚举给下一个池塘留下的时间,这里要从1开始枚举,因为给下一个池塘0分钟也没有意义
··然后此时在i池塘待j的收益就要因为下一个池塘所占有的时间而变化了,这j分钟里有一部分给了下一个池塘
··分配k分钟给下一个池塘,j-c[i]-k就是原池塘所有的时间,这也解释了为何第二层循环要到j<=c[i]的时候结束
··然后更新在这个池塘j分钟可以获得的最大收益,倒着向上状态转移
。。这里为什么要倒着转移,说实话感觉正着转移也可以。谁要是知道了踢我一下。
第二种做法,自上而下更新

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int a[N], b[N], c[N], presum[N];
int t, f[2][1010];
int main() {
#ifndef ONLINE_JUDGE
    freopen(".in", "r", stdin);
#else
    ios::sync_with_stdio(0);
#endif
    cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1; i <= n; ++i)
        cin >> b[i];
    for (int i = 2; i <= n; ++i) {
        cin >> c[i];
        presum[i] = c[i] + presum[i - 1];
    }
    cin >> t;
    n = lower_bound(presum + 1, presum + 1 + n, t) - presum - 1;
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        memset(f[i & 1], 0, sizeof f[0]);
        for (int j = presum[i] + 1; j <= t; j++) {
            f[i & 1][j] = f[(i - 1) & 1][j - c[i]];
            int sum = a[i];
            for (int k = 1; k <= min((a[i] + b[i] - 1) / b[i], j - presum[i]); k++) {
                f[i & 1][j] = max(f[i & 1][j], f[(i - 1) & 1][j - k - c[i]] + sum);
                sum += a[i] - k * b[i];
            }
            ans = max(ans, f[i & 1][j]);
        }
    }
    cout << ans;
}

这里用了滚动数组优化,其实一维数组应该也可以,但我是初学者,还不知道怎么改。 这里和第一种做法就不一样了,这里就把池塘转移的时间放在终点池塘上了。c[i]是从2开始读取的 ` n = lower_bound(presum + 1, presum + 1 + n, t) - presum - 1;` 这句话就是为了优化,找到第一个大于等于t的下标,因为在池塘间转移的时间不可能大于总的钓鱼时间吧,那还钓啥鱼呀,都去走路吧 这里举个简单的例子吧,假设i从2开始转移,也就是从第二个池塘开始转移,因为第一个池塘c[i]为0,以这个为例子讲转移不够清楚。假如第二个池塘对应的是f[1]数组,实际对应的是f[0]数组。这里我在大脑中还是设为f[1]好想

·先将f[1]数组清空,方便存下转移来的值
··第二重循环从presum[2]+1开始,因为这里求的是前缀和,也就是到第i个池塘一共花了多少时间在路上。这里是到第二个池塘,花了presum[2]的时间,从加1开始也是为了避免无效转移
··将上一行j分钟待的收益减去c[i],也就是走到i+1个池塘要花的时间。这里是c[2],也就是说上一个池塘的j要减去c[i],才得到转移到本池塘时j时间的收益。
··这里的f[i & 1][j] = f[(i - 1) & 1][j - c[i]]; 第二层循环的第一句话,是不给本池塘时间,相当于k=0,但是sum就要改为0开始了,因为一开始本池塘就没有收益。这里给一个我微改的版本也过了。(在下面)
··内层开始枚举收益,终止条件是在本池塘有鱼时应待的时间(收益大于0的时间)和理论上可以待的时间(只要总时间减去前面花在路上的时间都可以在这里钓鱼)
··f[(i - 1) & 1][j - k - c[i]] + sum(减去本池塘获得的时间k+c[i],就是上一个池塘获得的时间)
··状态转移完就可以更新答案了

第三种做法,贪心加多路归并

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 110;

int a[N], d[N], l[N], spend[N];

int get(int k)                                  //第i分钟在第k个鱼塘钓到鱼的数量
{
    return max(0, a[k] - d[k] * spend[k]);
}

int work(int n, int T)                          //只走前n个鱼塘,且时间为T的最大收益
{
    int res = 0;
    memset(spend, 0, sizeof spend);
    for (int i = 0; i < T; i ++ )               //按分钟遍历
    {
        int t = 1;                              //t表示第i分钟第t的鱼塘的鱼最多,初始为1号鱼塘
        for (int j = 1; j <= n; j ++ )          //第i分钟在前n个鱼塘的最大收益
            if (get(j) > get(t))
                t = j;

        res += get(t);
        spend[t] ++ ;                           //在t号鱼塘逗留时间+1
    }

    return res;
}

int main()
{
    int n, T;                                   //n个鱼塘,截止时间为T
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i]; //各鱼塘第一分钟产鱼量
    for (int i = 1; i <= n; i ++ ) cin >> d[i]; //各鱼塘每一分钟减鱼量
    for (int i = 2; i <= n; i ++ )              //从第一个鱼塘到第i个鱼塘之间的距离
    {
        cin >> l[i];
        l[i] += l[i - 1];                       //因为这步求前缀和,所以前面下标i要从1开始
    }
    cin >> T;

    int res = 0;
    for (int i = 1; i <= n; i ++ )              //从第一个鱼塘出发,遍历n个鱼塘
        res = max(res, work(i, T - l[i]));      //只去前i个鱼塘的最大收益,这里已经减去路上所用时间

    cout << res << endl;
}

 

··这里y总的思路太牛了,能想到这么难的多路归并算法,只能是%%%
··首先是贪心思想,有n个池塘,一定有n条路线,1到1也算是一条路线,就是一直待在第一个池塘不走。为什么一定只有n条路线呢,因为你在各个鱼塘间横跳,在各个鱼塘分配的时间和你一直走分配给每个鱼塘时间的总和一样。
··每次固定走到的终点,对每个鱼塘分配时间,这就是多路归并。肯定分配给可以出更多鱼的池塘,Y总这里用了两重循环,也就是O(n2),下面的堆对这个进行了优化。

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int>PIT;

const int N=110;
int n;
int a[N],s[N],l[N];
/**
 * 1262使用堆来进行实现
*/

int work(int m,int T)
{
    priority_queue<PIT>q;//大根堆
    for(int i=1;i<=m;i++)q.push({a[i],i});
    int res=0;
    for(int i=1;i<=T&&!q.empty();i++)
    {   
        auto t=q.top();
        q.pop();
        res+=t.first;
        t.first-=s[t.second];
        if(t.first>=0)q.push(t);//循环的判断条件中只要不是0就都判断为true,包括负数
    }
    return res;
}


int main()
{
    int T;
    cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&l[i]);
        l[i]+=l[i-1];
    }
    scanf("%d",&T);

    int res=0;
    for(int i=1;i<=n;i++)
    {
        res=max(res,work(i,T-l[i]));
    }
    cout<<res<<endl;
    return 0;
}

 

第四种写法就是将y总的代码改成堆写法了,为了方便找到公差,就选择了pair(a[i],i)把这个鱼塘编号也存进去了,这样方便找到公差。也是很厉害的思路。

总结:四种思路给我的感觉就是,动态规划的做法和背包模型很像,类似暴力。而多路归并的做法就很灵活了,反正在思维上都是我目前无法到达的境界。%%%~Orz,Orz

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值