题目类型 背包 | 对背包问题不了解的同学 猛戳 -> 背包九讲
题目意思
给出 n 件物品(1 <= n <= 2000)
每件物品有两个属性 Ti Ci (Ti 的意思是如果你取这件物品的话可以免费取其他的 Ti件物品 Ci的意思是如果你取这件物品需要花费
Ci)
问 把 n 件物品取完需要的最少花费是多少
解题方法
dp[i][j] -> 取完前 i 件物品后还可以免费取 j 件物品的最小花费
状态转移
不取第i件物品 dp[i][j-1] = min(dp[i][j-1], dp[i-1][j] (因为取完前i-1个物品后如果还可以免费取其他j个物品的话 就可以把第i个物品取了变 成dp[i][j-1])
取第i件物品 dp[i][j+第i件物品可以提供的免费数] = min(dp[i][j+第i件物品可以提供的免费数], dp[i-1][j] + 第i件物品所需的花费)
递推即可
注意
j的下标可能是负数 所以可以所有下标向右移若干位
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 2000 + 10;
typedef long long LL;
const LL INF = ((LL)1)<<62;
const int add = 2000;
int a[MAXN][2];
LL dp[MAXN][MAXN*2];
int main() {
int n;
while(scanf("%d", &n) != EOF) {
for( int i=0; i<n; i++ ) {
scanf("%d%d", &a[i][0], &a[i][1]);
}
for( int i=0; i<n; i++ ) for( int j=0; j<=4000; j++ ) dp[i][j] = INF;
dp[0][a[0][0]+add] = a[0][1];
dp[0][-1+add] = 0; //不选这件物品的话就意味着 取了这件物品还可以免费取 -1 件其他物品 加上add为了使下标不是负数,所有下标整体右移
for( int i=1; i<n; i++ ) {
for( int j=0; j<=4000; j++ ) {
if(j>0) dp[i][j-1] = min(dp[i][j-1], dp[i-1][j]);
if(j+a[i][0]<=4000) dp[i][j+a[i][0]] = min(dp[i][j+a[i][0]], dp[i-1][j] + a[i][1]);
}
}
LL res = INF;
for( int i=add; i<=4000; i++ ) res = min(res, dp[n-1][i]); //从add开始即从0开始, 表示取完了n件物品后(下标从0开始所以是n-1)
cout<<res<<endl; //还可以取其他至少0件物品的最小值, 因为dp数组的第二维非负才有实际意义
}
return 0;
}