题目描述
小 KKK 有 NNN 项工作等待完成,第 iii 项工作需要花 tit_iti 单位时间,必须在 did_idi 时刻或之前完成,报酬为 pip_ipi。
假设小 KKK 工作时刻从 000 开始,且同一时刻只能做一项工作、工作一旦开始则不可中断或切换至其他工作,请你帮小 KKK 规划一下如何选择合适的工作,使小 KKK 可以获得最多的报酬。
输入格式
输入第一行是一个正整数 TTT,表示数据的组数。
接下来有 TTT 组数据,每组数据第一行是一个正整数 NNN,表示待完成工作的数量。
接下来的 NNN 行,每行三个非负整数 ti,di,pit_i,d_i,p_iti,di,pi,表示第 iii 项工作需要花费的时间、截止时间以及报酬。
输出格式
对于每组数据,输出小 KKK 能获得最多的报酬是多少。
数据范围
1≤T≤51 \le T \le 51≤T≤5,
1≤N≤50001 \le N \le 50001≤N≤5000,
0≤ti,di,pi≤50000 \le t_i,d_i,p_i \le 50000≤ti,di,pi≤5000
输入样例:
3
5
1 2 50
3 3 100
1 5 1
3 2 5000
4 5 30
5
1 2 50
3 3 20
1 5 1
3 2 5000
4 5 30
5
1 2 50
3 3 100
1 5 1
3 2 5000
5 5 800
输出样例:
101
80
800
二维dp(6/10)内存超限代码
/*
max报酬 = 规定时间完成 + 完成的任务报酬尽量高
花的时间t和截止时间d可以做一个统一->最迟开始时间 (d-t), 在此时间之后便不能加入背包
(开始时间_i + t_i) 之间不能有重复
0-1背包 + 贪心
*/
// 在动态规划中,按截止时间排序是为了确保状态转移的无后效性。
#include <bits/stdc++.h>
using namespace std;
int rd()
{
int k = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
k = (k << 1) + (k << 3) + (c ^ '0'), c = getchar();
return k;
}
void wr(int x)
{
if (x > 9)
wr(x / 10);
putchar((x % 10) ^ '0');
}
// 定义工作任务的结构体
struct Job {
int t, d, p; // t是完成任务需要的时间,d是截止时间,p是完成后的报酬
// 为了能对工作按截止时间排序,重载小于运算符
bool operator<(const Job& other) const {
return d < other.d; // 截止时间早的工作排在前面
}
};
// 解决一组任务选择问题的函数
void solve() {
int n;
cin >> n; // 读入任务的数量
int max_d = 0; // 注意这里需要清空
// 存储所有任务,注意这里使用1-based索引(从1开始)
vector<Job> jobs(n + 1);
for (int i = 1; i <= n; i++) {
cin >> jobs[i].t >> jobs[i].d >> jobs[i].p; // 读入每个任务的时间、截止和报酬
max_d = max(max_d,jobs[i].d);
}
// 按截止时间对任务进行排序
// 就像我们做事情,先做截止时间早的,这样不容易耽误后面的事
sort(jobs.begin() + 1, jobs.end());
// dp数组是整个程序的核心!
// dp[i][j]表示:考虑前i个工作,在时间j前能获得的最大报酬
// 比如dp[3][10]就是考虑前3个工作,在时间10前能赚到的最多钱
vector<vector<int>> dp(n + 1, vector<int>(max_d + 1, 0));
// 遍历每个工作,逐步计算最优解
for (int i = 1; i <= n; i++) {
int t = jobs[i].t, d = jobs[i].d, p = jobs[i].p; // 当前工作的三个属性
// if (t > d) continue; 这句话不能加,因为后面的dp[i-1][j]需要更新
for (int j = 0; j <= max_d; j++) {
// 情况1:不选择做第i个工作
// 那么能得到的最大报酬就和只考虑前i-1个工作时一样
dp[i][j] = dp[i-1][j];
// 情况2:选择做第i个工作
// 我们需要检查在时间j前完成这个工作是否可能
// j >= t 确保开始做这个工作的时间不会是负数(因为开始时间是j-t)
// j <= d 确保这个工作能在截止时间前完成
if(j <= d && t <= j) {
dp[i][j] = max(dp[i][j], dp[i-1][j - t] + p);
}
}
}
int ans = 0;
for (int j = 0; j <= max_d; j++) {
ans = max(ans, dp[n][j]);
}
cout << ans << "\n";
}
int main() {
int t = rd();
while (t--) {
solve();
}
return 0;
}
一维dp AC代码
/*
max报酬 = 规定时间完成 + 完成的任务报酬尽量高
花的时间t和截止时间d可以做一个统一->最迟开始时间 (d-t), 在此时间之后便不能加入背包
(开始时间_i + t_i) 之间不能有重复
0-1背包 + 贪心
*/
// 在动态规划中,按截止时间排序是为了确保状态转移的无后效性。
#include <bits/stdc++.h>
using namespace std;
int rd(){
int res = 0;
char c = getchar();
while(!isdigit(c)){
c = getchar();
}
while(isdigit(c)){
res = (res << 1) + (res << 3) + (c^'0'), c = getchar();
}
return res;
}
void wr(int x)
{
if (x > 9)
wr(x / 10);
putchar((x % 10) ^ '0');
}
// 定义工作任务的结构体
struct Job {
int t, d, p; // t是完成任务需要的时间,d是截止时间,p是完成后的报酬
// // 为了能对工作按截止时间排序,重载小于运算符
// bool operator<(const Job& other) const {
// return d < other.d; // 截止时间早的工作排在前面
// }
};
// 解决一组任务选择问题的函数
void solve() {
int n = rd();
int max_d = 0; // 注意这里需要清空
// 存储所有任务,注意这里使用1-based索引(从1开始)
vector<Job> jobs(n + 1);
for (int i = 1; i <= n; i++) {
jobs[i].t = rd();
jobs[i].d = rd();
jobs[i].p = rd();
max_d = max(max_d,jobs[i].d);
}
// 按截止时间对任务进行排序
// 就像我们做事情,先做截止时间早的,这样不容易耽误后面的事
sort(jobs.begin() + 1, jobs.end(),
[](const Job& a,const Job& b){
return a.d < b.d;
});
// dp数组是整个程序的核心!
// dp[j]表示:考虑前i个工作,在时间j前能获得的最大报酬
vector<int> dp(max_d + 1, 0);
// 遍历每个工作,逐步计算最优解
for (int i = 1; i <= n; i++) {
int t = jobs[i].t, d = jobs[i].d, p = jobs[i].p; // 当前工作的三个属性
if (t > d) continue;
for (int j = max_d; j >= t; j--) {
// 情况1:不选择做第i个工作
// 那么能得到的最大报酬就和只考虑前i-1个工作时一样
// dp[i][j] = dp[i-1][j];
// 情况2:选择做第i个工作
// 我们需要检查在时间j前完成这个工作是否可能
// j >= t 确保开始做这个工作的时间不会是负数(因为开始时间是j-t)
// j <= d 确保这个工作能在截止时间前完成
dp[j] = max(dp[j], dp[min(j,d) - t] + p);
}
}
cout << dp[max_d] << "\n";
}
int main() {
int t = rd();
while (t--) {
solve();
}
return 0;
}
这里的重载小于函数可以替换为sort函数的自定义比较函数部分!
注意:
在ACwing上提交代码的时候一定需要开C++O2优化才可以通过。
当然读者也可以将vector数组改成普通的定长数组来Ac本题!