洛谷 P1509 找啊找啊找GF 题解
题目大意
给定 \(n\) 个物品,每个物品有 \(3\) 个限制条件 \(rmb_i,rp_i,time_i\),问在选出物品 \(rmb_i\) 之和不超过 \(m\),\(rp_i\) 之和不超过 \(r\) 的前提下选出尽量多的物品,求选出的物品 \(time_i\) 之和最小是多少。
F1:三维背包
一道多维 0-1 背包的题目。由于题目中有 \(rmb_i,rp_i,time_i\) \(3\) 个限制条件,所以我们定义 \(dp_{j,k,l}\) 表示 \(rmb_i\) 之和不超过 \(j\),\(rp_i\) 之和不超过 \(k\),选择至少 \(l\) 个物品,\(time_i\) 和的最小值。
那么初始化阶段,根据定义及题目最下方的提示,\(dp_{j,k,0}=0\);而由于求最小值,所以其它元素赋最大值即可。状态转移则并不特别,此处不再赘述。其中 \(i\) 枚举物品,\(j,k,l\) 含义同上。
代码如下,时间复杂度 \(O(n^2mr)\),空间复杂度 \(O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,r;
int rmb[N],rp[N],t[N],dp[N][N][N]; // time 为库函数名,用 t 代替
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d%d%d",rmb+i,rp+i,t+i);
scanf("%d%d",&m,&r);
memset(dp,0x3f,sizeof dp);
for (int i=0;i<=m;++i){
for (int j=0;j<=m;++j) dp[i][j][0]=0;
}
for (int i=1;i<=n;++i){
for (int j=m;j>=rmb[i];--j){
for (int k=r;k>=rp[i];--k){
for (int l=n;l>=1;--l)
dp[j][k][l]=min(dp[j][k][l],dp[j-rmb[i]][k-rp[i]][l-1]+t[i]);
}
}
}
for (int i=n;i>=0;--i){
if (dp[m][r][i]!=0x3f3f3f3f){ printf("%d",dp[m][r][i]);break; }
}
return 0;
}
F2:多数组
重新定义代价与价值。按照题目说法,要求选出物品的 \(rmb_i\) 之和不超过 \(m\),\(rp_i\) 之和不超过 \(r\),同时最大化选出物品数量,再此基础上选择 \(time_i\) 之和最小的方案。所以其中代价有 \(rmb_i\) 与 \(rp_i\),价值有物品数量(\(1\))与 \(time_i\)。
观察到这里出现了两个价值,而我们之前都只有一个价值。之前一个 \(dp\) 数组代表一个价值,但现在有两个价值,可以想到开两个数组 \(f_{j,k},g_{j,k}\) 分别表示所选物品数量最大值与选出物品 \(time_i\) 之和最小值。
根据题意,首先要满足选出物品数量最多。如果选当前物品后总物品数量会更多,肯定要选,然后更新 \(f_{j,k}\) 和 \(g_{j,k}\),代码如下。
if (f[j][k]<f[j-rmb[i]][k-rp[i]]+1){
f[j][k]=f[j-rmb[i]][k-rp[i]]+1;
g[j][k]=g[j-rmb[i]][k-rp[i]]+time[i];
}
若选当前物品后,总物品数量持平,但 \(time_i\) 之和更小,也要选,即当物品数量不变时,更新 \(g_{j,k}\) 为两种方案的最小值。此处由于总物品数量与之前相同,说明 \(g_{j,k}\) 已在前面被直接赋值更新过了,所以 \(g_{j,k}\) 无需初始化为最大值,代码如下。
if (f[j][k]==f[j-rmb[i]][k-rp[i]]+1)
g[j][k]=min(g[j][k],g[j-rmb[i]][k-rp[i]]+time[i]);
最后输出答案时,直接输出 \(g_{m,r}\) 即可。时间复杂度 \(O(nmr)\),空间复杂度 \(O(mr)\),代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,r;
int rmb[N],rp[N],t[N],f[N][N],g[N][N];
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d%d%d",rmb+i,rp+i,t+i);
scanf("%d%d",&m,&r);
for (int i=1;i<=n;++i){
for (int j=m;j>=rmb[i];--j){
for (int k=r;k>=rp[i];--k){
if (f[j][k]<f[j-rmb[i]][k-rp[i]]+1)
f[j][k]=f[j-rmb[i]][k-rp[i]]+1,g[j][k]=g[j-rmb[i]][k-rp[i]]+t[i];
else if (f[j][k]==f[j-rmb[i]][k-rp[i]]+1)
g[j][k]=min(g[j][k],g[j-rmb[i]][k-rp[i]]+t[i]);
}
}
}
printf("%d",g[m][r]);
return 0;
}
F3:次要性动态规划
这里介绍一下一种新奇的题型:次要性动态规划。
一、题型特点
所谓次要性动态规划,指的是有多个条件,要求条件 \(1\) 最优的前提下使条件 \(2\) 最优,条件 \(2\) 再达到最优时让条件 \(3\) 也最优……即条件有主次之分并依次在可能范围内达到最优的动态规划,叫作次要性动态规划。
二、题型做法
首先要寻找题目条件。本题中条件即背包中的价值,有主要条件最大化选出物品数量及次要条件最小化选出物品 \(time_i\) 之和。
那么是不是可以认为物品数量对答案起决定性作用,其影响程度远远大于 \(time_i\) 对答案的影响程度,因为题目要在最大化选出物品数量的前提下再最小化 \(time_i\) 之和。
或者说,我们可以用哈希的思想,把每个物品数量和 \(time_i\) 两个价值变为一个价值 \(p_i\),使题目变为最大化选出物品的 \(p_i\) 之和。根据题目所给的数据范围,可以令 \(p_i=100010-time_i\),既保证了 \(time_i\) 的最小化对应了 \(p_i\) 的最大化,又满足 \(p_i>0\),即物品数量比 \(time_i\) 更影响答案。
最终处理答案时,\(dp\) 数组记录的是 \(p_i\) 之和,而所求为 \(\sum_{i=1}^k{time_i}\)。由下述推理,可得所求即为最后一行。
所以,我们只需把每个物品的价值看作 \(100010-time_i\) 进行 0-1 背包,时间复杂度 \(O(nmr)\),空间复杂度 \(O(mr)\)。代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=105,M=100010;
int n,m,r;
int rmb[N],rp[N],t[N],dp[N][N];
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d%d%d",rmb+i,rp+i,t+i);
scanf("%d%d",&m,&r);
for (int i=1;i<=n;++i){
for (int j=m;j>=rmb[i];--j){
for (int k=r;k>=rp[i];--k)
dp[j][k]=max(dp[j][k],dp[j-rmb[i]][k-rp[i]]+M-t[i]);
}
}
printf("%d",M-dp[m][r]%M);
return 0;
}

浙公网安备 33010602011771号