洛谷p1833 樱花(混合背包+二进制优化板子)

题目链接:

樱花 - 洛谷https://www.luogu.com.cn/problem/P1833参考了别人的博客:洛谷 P1833 樱花 背包+二进制拆分_一条自私的鱼的博客-CSDN博客

思路:

可以看有限次的樱花树是多重背包,无限次的樱花树是完全背包,完全背包经过优化之后的复杂度和01背包一样,很好处理,但是多重背包在朴素做法下必须加一层循环,复杂度较高。本题用这种朴素做法只能在开了O2优化的情况下ac,朴素做法代码如下。

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll t[maxn], c[maxn], p[maxn]; //时间,美学值,次数(0表示无限次)
ll dp[maxn]; //外层for是遍历物品,内层for是遍历背包(限制时间),dp值是总美学值

signed main(){
    //io
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    //input
    ll shh, smm, ehh, emm, n; //开始时间,结束时间,樱花树数量
    scanf("%lld:%lld", &shh, &smm);
    scanf("%lld:%lld", &ehh, &emm);
    scanf("%lld", &n);
    ll time1 = shh*60 + smm;
    ll time2 = ehh*60 + emm;
    ll time = time2 - time1;

    for(int i=1; i<=n; i++){
        scanf("%lld %lld %lld", &t[i],&c[i],&p[i]);
    }

    //solve and output
    memset(dp, 0, sizeof(dp));
    for(int i=1; i<=n; i++){ //遍历物品(当前的樱花树)
        if(p[i] == 0){ //无限次看
            for(int j=t[i]; j<=time; j++){ //多重背包,从左往右遍历背包,即当前限制时间(注意剪枝,从j=t[i]开始,而不是从1开始)
                dp[j] = max(dp[j], dp[j-t[i]] + c[i]);
            }
        }
        else{
            // 多重背包的技巧,在这一层重复p[i]次循环,就相当于买了1~p[i]次物品取最大总价值
            // for(int k=1; k<=p[i]; k++){
            //     for(int j=time; j>=t[i]; j--){ //和上面一样注意剪枝,还有要从右往左遍历(类似01背包)
            //         dp[j] = max(dp[j], dp[j-t[i]] + c[i]);
            //     }
            // }
            //也可以用下面这种写法(个人认为这种比较好理解,我一般写这种)
            for(int j=time; j>=t[i]; j--){
                for(int k=1; k<=p[i] && j>=k*t[i]; k++){
                    dp[j] = max(dp[j], dp[j-k*t[i]] + k*c[i]);
                }
            }
        }
        
    }
    cout << dp[time] << endl;
}

 

下面是二进制优化过的代码写法,经过二进制拆分,可以把多重背包和完全背包都转成01背包来处理,非常的方便(完全背包我直接看成数量足够多的多重背包处理了,虽然复杂度高一些,但是一般问题不大)

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll t[maxn], c[maxn], p[maxn]; //时间,美学值,次数(0表示无限次)
ll dp[maxn]; //外层for是遍历物品,内层for是遍历背包(限制时间),dp值是总美学值
ll shh, smm, ehh, emm, n; //开始时间,结束时间,樱花树数量
ll cnt, weight[100000], value[100000]; //分出物品的数量,重量和价值

//二进制拆分板子
inline void pre(){
    for(int i=1; i<=n; i++){
        int k = 1; //当前物品重量倍数
        while(p[i] > 0){ //当物品还没被分完的时候
            cnt++; //分出一个物品
            weight[cnt] = k*t[i]; //重量(耗时)
            value[cnt] = k*c[i]; //价值(美学值)
            p[i] -= k; k *= 2; //次数减少k,下次分割的倍数加倍
            if(p[i] < k){ //如果剩下的不能再拆成上一份的两倍,就直接放在一起
                cnt++;
                weight[cnt] = t[i] * p[i]; //剩下的全部重量(耗时)
                value[cnt] = c[i] * p[i]; //剩下全部价值(美学值)
                break;
            }
        }
    }
}
int main(){
    //io
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    //input
    scanf("%lld:%lld", &shh, &smm);
    scanf("%lld:%lld", &ehh, &emm);
    scanf("%lld", &n);
    ll time1 = shh*60 + smm;
    ll time2 = ehh*60 + emm;
    ll time = time2 - time1;

    for(int i=1; i<=n; i++){
        scanf("%lld %lld %lld", &t[i],&c[i],&p[i]);
        if(p[i] == 0) p[i] = 999999;
    }

    //solve
    pre(); //二进制拆分
    for(int i=1; i<=cnt; i++){ //遍历所有经过拆分的物品(樱花树)
        for(int j=time; j>=weight[i]; j--){ //遍历当前背包(可用时间),01背包模板
            dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
        }
    }
    cout << dp[time] << endl;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值