记录105
cpp
#include<bits/stdc++.h>
using namespace std;
int dp[5010];
int ti[110];
int main(){
int a,a1,a2,m,n;//二人比例,同学水平,老师水平,题目数,知识数
cin>>a1>>a2;//同学水平,老师水平
a=a2/a1;//二人比例
cin>>m>>n;//题目的总数m 和知识点的总数n
int t;//对应知识点的时间
for(int i=1;i<=n;i++){//n个知识点
cin>>t;//输入时间
ti[i]=t*a;//同学做这个知识点的时间
}
int p,q;//p表示该题目所属的知识点,q表示该题目对应的奖励值
for(int i=1;i<=m;i++){//m个题目
cin>>p>>q;//输入
for(int j=5000;j>=ti[p];j--){//背包装
dp[j]=max(dp[j],dp[j-ti[p]]+q);//一直装下去
}
}
int num;//规定时间
cin>>num;//输入
cout<<dp[num];//输出
return 0;//结束程序
}
题目传送门
https://www.luogu.com.cn/problem/P2430
突破口
WKY 和老王都有一个水平值,他们水平值的比值和做这些题所用时间的比值成反比。比如如果 WKY 的水平值是 1,老王的水平值是 2,那么 WKY 做同一道题的时间就是老王的 2 倍。
现在 WKY 同学请你帮忙,计算在老王规定的时间内,WKY 所能得到最大奖励值是多少 。
🔍 一、题目核心理解
🎯 问题描述
- 有 m 道题目 ,每道题属于某个知识点 p(1 ≤ p ≤ n)
- 每个知识点 i 有一个 老王做该类题的时间 t_i
- WKY 的水平值为
a1,老王为a2,且a2是a1的整数倍 - WKY 做某知识点题的时间 = 老王时间 × (a2 / a1)
- 因为:水平比 = a1 : a2 → 时间比 = a2 : a1(反比)
✅ 举例:WKY 水平=1,老王=2 → WKY 花费时间是老王的 2 倍
- 每道题有一个奖励值 q
- 给定总时间上限
num - 目标 :选择若干题目,在总时间 ≤
num的前提下,最大化总奖励值
💡 注意:同一知识点下的不同题目,对 WKY 来说耗时相同(因为只依赖知识点)
→ 这是一个标准的 0-1 背包问题:
- 每道题是一个物品
- "重量" = 该题所属知识点的 WKY 耗时
- "价值" = 奖励值 q
- 背包容量 = 总时间上限
📌 输入样例解析(输入 #1)
cpp
1 2 → a1=1, a2=2 → 比例 a=2
6 4 → m=6题, n=4知识点
1 2 3 4 → 老王做各知识点时间
题目:
1 5 → 知识点1,奖励5 → WKY耗时=1×2=2
2 6 → 耗时=2×2=4,奖励6
3 3 → 耗时=3×2=6,奖励3
4 8 → 耗时=4×2=8,奖励8
3 3 → 耗时=6,奖励3
4 5 → 耗时=8,奖励5
20 → 总时间上限
可能的最优选择:
- 选题1(2,5)、题2(4,6)、题3(6,3)、题5(6,3)、题6(8,5)
- 总时间 = 2+4+6+6+8 = 26 > 20 ❌
尝试:
- 题1(2,5) + 题2(4,6) + 题4(8,8) + 题5(6,3) → 2+4+8+6=20,奖励=5+6+8+3=22 ✅
输出:22
🧠 二、解题思路:0-1 背包
步骤
- 读入 WKY 和老王的水平值,计算时间放大比例
a = a2 / a1 - 读入每个知识点的老王耗时,乘以
a得到 WKY 耗时ti[i] - 对每道题:
- 知道它属于知识点
p - 它的"重量" =
ti[p] - "价值" =
q
- 知道它属于知识点
- 做标准 0-1 背包:
dp[j]= 在时间 j 内能获得的最大奖励- 倒序更新
- 输出
dp[num]
代码分析
cpp
#include<bits/stdc++.h>
using namespace std;
int dp[5010]; // dp[t] = 时间t内能获得的最大奖励(时间上限≤5000)
int ti[110]; // ti[i] = WKY做知识点i的题目所需时间(n≤100)
dp大小 5010:因规定时间 ≤5000ti大小 110:因 n ≤100
cpp
int main(){
int a, a1, a2, m, n;
cin >> a1 >> a2; // WKY水平,老王水平
a = a2 / a1; // 时间放大比例(整数)
- 题目保证
a2是a1的整数倍 →a为整数
cpp
cin >> m >> n; // 题目总数m,知识点总数n
cpp
int t;
for(int i = 1; i <= n; i++){
cin >> t; // 老王做知识点i的时间
ti[i] = t * a; // WKY做该知识点题的时间
}
- 预处理每个知识点的 WKY 耗时
cpp
int p, q;
for(int i = 1; i <= m; i++){
cin >> p >> q; // 题目所属知识点p,奖励q
for(int j = 5000; j >= ti[p]; j--){
dp[j] = max(dp[j], dp[j - ti[p]] + q);
}
}
🔄 背包更新:
- 每道题视为一个独立物品
- "重量" =
ti[p](该题所属知识点的 WKY 耗时) - "价值" =
q - 倒序遍历时间
j,防止重复选择(0-1 背包标准做法)
✅ 即使多个题目属于同一知识点,它们的"重量"相同,但仍是不同物品,可分别选或不选
cpp
int num;
cin >> num; // 规定的时间上限
cout << dp[num]; // 输出最大奖励
return 0;
}
dp[num]表示在时间 ≤ num 下的最大奖励(因为 DP 是"不超过容量"的最大值)
⚠️ 关键细节说明
| 细节 | 说明 |
|---|---|
dp 初始化? |
全局数组 → 自动初始化为 0,正确(不选任何题,奖励为 0) |
| 为什么倒序? | 防止同一题目被多次选择(虽然是不同题,但若正序,可能在一轮中多次更新) |
| 同一知识点多题? | 没问题!每道题独立处理,即使耗时相同,也是不同物品 |
| 时间放大比例? | a = a2 / a1,题目保证整除,无精度问题 |
总结:问题与解法对应
| 题目要素 | 背包模型 |
|---|---|
| 每道题 | 物品 |
| WKY 做题时间 | 重量(由知识点决定) |
| 奖励值 | 价值 |
| 总时间上限 | 背包容量 |
| 最大化奖励 | 0-1 背包求最大价值 |