一、题目描述
小朋友考试得第一名就可以得到零食奖励。
现在价格A、B、C、D、E、…,元商品名A1、B1、C1、D1、E1、…,小朋友的喜爱度依次为A2、B2、C2、D2、E2、…。
请返回选取x元零食可以达到的最大喜爱度。
二、输入描述
第一行输入为x和N,x为可使用的钱的总额,N为零食种类数。
0 ≤ x ≤ 1000
0 ≤ N ≤ 100
第二行开始为零食属性,每行有三个整型数值,分别代表零食的价格、数量和喜爱度。
零食价格(0,100)
零食个数[0,10000]
零食喜爱度[0,10000]
三、输出描述
最大喜爱度
四、测试用例
测试用例1:
1、输入
10 2
2 4 3
3 3 4
2、输出
14
3、说明
可选第1种零食最多 4 个,第2种最多 3 个。最佳选择为:选 2 个第2种(花费 6 元,喜爱度 8)和 2 个第1种(花费 4 元,喜爱度 6),总计花费 10 元,总喜爱度 14。输出 14。
测试用例2:
1、输入
6 7
3 1 8
4 1 2
3 1 1
9 1 7
4 1 1
5 1 8
4 1 4
2、输出
9
3、说明
6元可以购买两个3元的零食,喜爱度综合为8+1=9
五、解题思路
本题本质上是一个有限背包问题。题目给定零食的价格、可购买数量及喜爱度,要求在不超过总金额 x 的条件下,使所选零食的总喜爱度最大。由于每种零食的数量有限,直接使用 0/1 背包方法会遇到数量限制问题。
对于每种零食,如果数量较大,可以利用二进制拆分的技巧,将数量拆分为若干个“份”,每份视为一个 0/1 物品。例如,若数量为 13,则可拆分为 1, 2, 4, 6(6 = 13-1-2-4)。这样每个子问题就转换为 0/1 背包问题,从而降低时间复杂度。
使用 dp 数组,其中 dp[j] 表示在花费 j 元时能够获得的最大喜爱度。由于总金额 x 最大为 1000,使用一维数组即可。
当每种零食数量较大时,如果直接循环每个单位可能超时。采用二进制拆分,将数量拆分为若干个物品,转化为 0/1 背包问题,可有效减少状态转移次数。
六、Java算法源码
public class OdTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt(); // 可用的总金额
int N = sc.nextInt(); // 零食种类数
int[] dp = new int[x + 1]; // dp[j]表示花费j元时能获得的最大喜爱度
for (int i = 0; i < N; i++) {
int price = sc.nextInt(); // 零食价格
int count = sc.nextInt(); // 零食数量
int value = sc.nextInt(); // 零食喜爱度
int k = 1;
// 利用二进制拆分,将count分解为多个01背包问题的物品
while (count > 0) {
int num = Math.min(k, count); // 当前拆分的数量
int cost = price * num; // 当前拆分的总价格
int val = value * num; // 当前拆分的总喜爱度
// 从后向前遍历更新dp数组(防止重复使用)
for (int j = x; j >= cost; j--) {
dp[j] = Math.max(dp[j], dp[j - cost] + val);
}
count -= num;
k *= 2;
}
}
System.out.println(dp[x]); // 输出最大喜爱度
sc.close();
}
}
七、Python算法源码
# Python代码实现有限背包问题,采用二进制拆分方法
import sys # 导入sys模块用于读取标准输入
def main():
# 读取所有输入数据,并将其转换为整数列表
data = list(map(int, sys.stdin.read().split()))
if not data:
return
x = data[0] # 总金额
N = data[1] # 零食种类数
dp = [0] * (x + 1) # 初始化dp数组,dp[j]表示花费j元时获得的最大喜爱度
index = 2 # 从数据列表的第3个数字开始为各零食属性
# 遍历每种零食
for _ in range(N):
price = data[index] # 零食价格
count = data[index+1] # 零食数量
value = data[index+2] # 零食喜爱度
index += 3 # 更新索引,指向下一组零食数据
k = 1
# 利用二进制拆分,将count拆分为多个0/1背包问题的物品
while count > 0:
num = min(k, count) # 当前拆分的数量
cost = price * num # 当前拆分的总价格
val = value * num # 当前拆分的总喜爱度
# 从后向前遍历更新dp数组,防止重复使用同一物品
for j in range(x, cost - 1, -1):
dp[j] = max(dp[j], dp[j - cost] + val)
count -= num # 更新剩余数量
k *= 2 # 递增倍数
# 输出最终结果
print(dp[x])
if __name__ == "__main__":
main()
八、JavaScript算法源码
'use strict';
// Node.js实现有限背包问题,采用二进制拆分方法
process.stdin.resume(); // 启动标准输入
process.stdin.setEncoding('utf8'); // 设置输入编码为utf8
let inputData = ''; // 用于存储所有输入数据
// 监听数据输入事件
process.stdin.on('data', function(chunk) {
inputData += chunk;
});
// 输入结束后处理数据
process.stdin.on('end', function() {
// 按空白字符拆分输入数据,并转换为数字数组
let data = inputData.trim().split(/\s+/).map(Number);
if(data.length === 0) return;
let x = data[0]; // 可用的总金额
let N = data[1]; // 零食种类数
let dp = new Array(x + 1).fill(0); // 初始化dp数组,dp[j]表示花费j元时获得的最大喜爱度
let index = 2; // 数据起始位置(零食属性从此处开始)
// 遍历每种零食
for(let i = 0; i < N; i++){
let price = data[index]; // 零食价格
let count = data[index+1]; // 零食数量
let value = data[index+2]; // 零食喜爱度
index += 3; // 移动到下一组零食数据
let k = 1;
// 二进制拆分,将count拆分为多个0/1背包问题的物品
while(count > 0){
let num = Math.min(k, count); // 当前拆分的数量
let cost = price * num; // 当前拆分的总价格
let val = value * num; // 当前拆分的总喜爱度
// 从后向前遍历,更新dp数组(防止重复使用)
for(let j = x; j >= cost; j--){
dp[j] = Math.max(dp[j], dp[j - cost] + val);
}
count -= num; // 更新剩余数量
k *= 2; // 更新倍数
}
}
// 输出最终结果
console.log(dp[x]);
});
九、C算法源码
#include <stdio.h>
#include <stdlib.h>
// 定义一个函数用于返回两个数中的较大值
int max(int a, int b) {
return a > b ? a : b;
}
int main(){
int x, N;
// 读取总金额x和零食种类数N
if(scanf("%d %d", &x, &N) != 2) return 1;
// 动态分配dp数组,dp[j]表示花费j元时能获得的最大喜爱度,初始值为0
int *dp = (int*)calloc(x + 1, sizeof(int));
// 遍历每种零食
for(int i = 0; i < N; i++){
int price, count, value;
// 读取当前零食的价格、数量和喜爱度
scanf("%d %d %d", &price, &count, &value);
int k = 1;
// 二进制拆分,将count拆分为多个0/1背包问题的物品
while(count > 0){
int num = k < count ? k : count; // 当前拆分的数量(取k和剩余数量中的较小值)
int cost = price * num; // 当前拆分的总价格
int val = value * num; // 当前拆分的总喜爱度
// 从后向前遍历更新dp数组,保证每个子物品只使用一次
for(int j = x; j >= cost; j--){
dp[j] = max(dp[j], dp[j - cost] + val);
}
count -= num; // 更新剩余数量
k *= 2; // 更新倍数
}
}
// 输出最大喜爱度结果
printf("%d\n", dp[x]);
free(dp); // 释放动态分配的内存
return 0;
}
十、C++算法源码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int x, N;
// 读取总金额x和零食种类数N
cin >> x >> N;
vector<int> dp(x + 1, 0); // 初始化dp数组,dp[j]表示花费j元时获得的最大喜爱度
// 遍历每种零食
for (int i = 0; i < N; i++) {
int price, count, value;
// 读取当前零食的价格、数量和喜爱度
cin >> price >> count >> value;
int k = 1;
// 利用二进制拆分,将count拆分为多个0/1背包问题的物品
while (count > 0) {
int num = min(k, count); // 当前拆分的数量
int cost = price * num; // 当前拆分的总价格
int val = value * num; // 当前拆分的总喜爱度
// 从后向前遍历,更新dp数组(防止重复使用同一物品)
for (int j = x; j >= cost; j--) {
dp[j] = max(dp[j], dp[j - cost] + val);
}
count -= num; // 更新剩余数量
k *= 2; // 更新倍数
}
}
// 输出最终的最大喜爱度
cout << dp[x] << endl;
return 0;
}