Skip to content

二进制分组优化

题目

题目描述

爱与愁大神后院里种了 n 棵樱花树,每棵都有美学值 C₁(0 ≤ Cᵢ ≤ 200)。爱与愁大神在每天上学前都会来赏花。爱与愁大神可是生物学霸,他懂得如何欣赏樱花:

  • 一种樱花树看一遍过,
  • 一种樱花树最多看 Pᵢ(0 ≤ Pᵢ ≤ 100)遍,
  • 一种樱花树可以看无数遍。

但是看每棵樱花树都有一定的时间 Tᵢ(0 ≤ Tᵢ ≤ 100)。

爱与愁大神离去上学的时间只剩下一小会儿了。

求解:看哪几棵樱花树能使美学值最高,且他能准时(或提早)去上学。

输入格式

共 n+1 行。

第 1 行:现在时间 Tₛ(格式:hh:mm),去上学的时间 Tₑ(格式:hh:mm),樱花树数量 n。

其中 0 ≤ hh ≤ 23,0 ≤ mm ≤ 59,hh、mm、n 为正整数。

第 2 行到第 n+1 行,每行三个正整数:Tᵢ Cᵢ Pᵢ

  • Tᵢ:看完第 i 棵树的耗时
  • Cᵢ:第 i 棵树的美学值
  • Pᵢ:最多可看的次数(0 表示可无限次)

输出格式

只有一个整数,表示最大美学值。

输入输出样例 #1

输入 #1

6:50 7:00 3
2 1 0
3 3 1
4 5 4

输出 #1

11

说明/提示

100% 数据满足:Te - Ts ≤ 1000(即开始时间距离结束时间不超过 1000 分钟),n ≤ 10000。保证 Te、Ts 为同一天内的时间。

样例解释

爱与愁大神有 10 分钟时间赏花。

他选择赏第一棵樱花树 1 次,赏第三棵樱花树 2 次,总花费时间:2 + 4 × 2 = 10 分钟。

获得的美学值为:1 + 5 × 2 = 11。

思路

本题为标准混合背包模板,包括01背包(选/不选)、多重背包(每个物品有限次数选择)和完全背包(每个物品不限次数选择),其通用思路为:

  • 将多重背包转换为复数个使用二进制分组优化的01背包
  • 单开状态数组记录每个物品为01背包(包括多重背包转换为的01背包)和完全背包
  • 进入正常背包问题的dp[i]外层循环
  • 根据状态数组决定进入的内层循环是01背包的倒序还是完全背包的顺序
  • 输出dp[t](t为最大时间)

注意:01背包和完全背包的内层for遍历顺序不同,如果采用01背包的两个dp数组回滚会导致完全背包的部分在计算时出错,因此对于这类问题应该只用一个dp数组,所有操作都在原来的数组上修改。


二进制分组:原理是任意一个整数 P,都可以表示成若干个 2 的幂次之和 ,而使用这种拆分能在保证dp结果相同时降低时间复杂度。

内层循环的顺序:完全背包和01背包的模板上唯一的区别是内层循环的顺序和倒序,其中,01背包倒序是为了防止同一物品被重复选取


for循环嵌套:01背包中两层for循环判断的本质是是否将第i个物品放入最大容量为j的背包中。

dp[j-w[i]]+w[i]:状态转移方程中的这一部分是选择当前物品时的最大价值

如果要在容量为j的背包中装下当前物品,那么可以剩下的能装物品的最大容积就是j-w[i],用这时的最大价值加上当前物品的价值,结果就是如果取当前物品,背包中能装下的最大价值。

因为当前物品是有体积的,而背包已经装了一部分东西,如果要装下当前物品,就必须舍弃部分物品,留出可以装下当前物品的容积。

Code

#include<iostream>
#include<cstring>
using namespace std;

const int N = 70005;
int h1=0, m1=0, h2=0, m2 = 0;
char _a;    //滤掉时间输入时的字符
int t;       //时间
int tree;    //树的数目
int n=1;       //分堆用的计数器;
int cost, value, num;     //耗费时间,美学价值,可看次数
int c[N] = { 0 }, v[N] = { 0 };     // 二进制分堆后的耗费时间和美学价值;
bool state[70005] = { false };      //ture:01背包;false:完全背包

int dp[1005] = { 0 };      //dp

int main(){
    cin >> h1 >> _a >> m1;
    cin >> h2 >> _a >> m2;
    cin >> tree;
    t = 60 * (h2 - h1) + (m2 - m1);     //最大时间

    for (int x = 1;x <= tree ; x++) {
        cin >> cost >> value >> num;
        if (num == 0) {
            c[n] = cost;
            v[n] = value;
            state[n] = false;
            n++;
        }
        else {
            for (int i = 1;i <= num;i<<=1) {
                c[n] = cost * i;
                v[n] = value * i;
                state[n] = true; // 01背包
                num -= i;
                n++;
            }
            if (num!=0) {
                c[n] = cost * num;
                v[n] = value * num;
                state[n] = true;
                n++;
            }
        }


    }
    for (int i = 1;i < n;i++) {
            //01背包
        if (state[i]) {
            for (int j = t;j >= 1;j--) {
                if (c[i] > j)continue;
                dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
            }
        }
        //完全背包
        else {
            for (int j = 1;j <=t;j++) {
                if (c[i] > j)continue;
                dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
            }
        }
    }
    cout<<dp[t];

    return 0;
}