洛谷 P1509 找啊找啊找GF 题解

题目大意

洛谷 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\) 含义同上。

\[dp_{j,k,l}=\min{(dp_{j,k,l},dp_{j-rmb_i,k-rp_i,l-1}+time_i)} \]

代码如下,时间复杂度 \(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}\)。由下述推理,可得所求即为最后一行。

\[\begin{aligned} &\text{记 $M=100010$,选出 $k$ 个数的权值为 $w_i$} \\ &\Rightarrow w_i=M-time_i\\ &\Rightarrow dp_{m,r}=\sum_{i=1}^k{w_i}=k\cdot M-\sum_{i=1}^k{time_i} \\ &\Rightarrow \sum_{i=1}^k{time_i}=k\cdot M-dp_{m,r} \\ &\text{同时上式根据题意小于 $M$,而 $k\cdot M$ 为 $M$ 的倍数} \\ &\Rightarrow \sum_{i=1}^k{time_i}=k\cdot M-((k-1)M+dp_{m,r}\bmod M) \\ &\Rightarrow\text{所求}=M-dp_{m,r}\bmod M \end{aligned} \]

所以,我们只需把每个物品的价值看作 \(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;
}
posted @ 2026-01-10 21:04  CodingJuRuo  阅读(2)  评论(0)    收藏  举报