背包问题
Basic Knowledge
01 背包,完全背包,分组背包,多重背包。
Problems
HDU3466 Proud Merchants
Description
\(n\) 个物品,每个物品价值为 \(v_i\),\(m\) 的背包体积。只有当背包剩余体积不少于 \(\max\{p_i,q_i\}\) 时,才能将物品 \(i\) 装入背包,占用 \(p_i\) 的体积。求背包内物品的价值和的最大值。
Solution
此题需要考虑选择物品的顺序。
用贪心的邻项交换思想,考虑物品 \(i\) 和 \(j\),若要都选,所需要剩下的最少体积。若先选 \(i\) 所需要剩下的最小体积更小,则有 \(\max\{q_i,p_i+q_j\}<\max\{q_j,p_j+q_i\}\)。
- 当 \(q_i>p_i+q_j\) 时,\(q_i<p_j+q_i\),显然成立。
- 当 \(q_j>p_j+q_i\) 时,\(p_i+q_j<q_j\),显然不成立。
以上两种情况我们都无法改变,故不用考虑。所以考虑 \(p_i+q_j<p_j+q_i\)。则 \(p_i-q_i<p_j-q_j\)。所以 \(p_i-q_i\) 较小的需要先选。排序后用普通 01 背包即可。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505,M=5005,INF=0xc0c0c0c0;
struct Merchant{
int p,q,v;
friend bool operator<(const Merchant&A,const Merchant&B){
return A.p-A.q<B.p-B.q;
}
}g[N];
int n,m,f[M],ans;
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
memset(f,0xc0,sizeof(f));
ans=0;
f[0]=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&g[i].p,&g[i].q,&g[i].v);
}
sort(g+1,g+n+1);
for(int i=1;i<=n;i++){
for(int j=m+min(g[i].p-g[i].q,0);j>=g[i].p;j--){
f[j]=max(f[j],f[j-g[i].p]+g[i].v);
}
}
for(int i=0;i<=m;i++)ans=max(ans,f[i]);
printf("%d\n",ans);
}
return 0;
}
Inspiration
此题为什么没看题解时没有做出来?难点在于如何列出微扰式子。都知道要交换邻项,但比较什么?我们应该代入背包问题:体积有限,如何让能够塞入的物品尽量多(价值尽量大已经被背包自身解决)。价值已经不用考虑,故为使背包尽量塞入更多,最好同时塞入两者。又因为背包体积不确定,所以要使得同时塞入两者的最小体积尽量大。
思路:已经解决的不用再考虑,不能在排序时确定的也不用考虑。
HDU3033 I love sneakers!
Description
\(n\) 件物品,\(m\) 的背包体积。\(k\) 组,每个物品都归到一组中,求每组至少选一个能获得的最大价值。(\(n\le 100\),\(m\le 10^4\),\(k\le 10\))
Solution
普通 01 背包不能解决每组都至少选一个的要求。看看其状态转移方程:
也就是说,当不选更优时,它不会去选。
为了强制它在每组中至少选一个,我们按组来划分阶段:
这样它就只能选了。但是这样每组只能选一个,因为每次都从上一组转移过来。所以要尝试从本组转移:
思路非常清晰:每组中的物品只能选,不能不选,没有继承。对于每个体积,所有的比较都是在组内进行的,所以保证了这个体积在这一组中至少选一个。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=105,K=15,M=1e4+5,INF=0xc0c0c0c0;
int n,m,k,a[N],v[N],c[N],f[N][M];
vector<int>p[K];
int main(){
while(scanf("%d%d%d",&n,&m,&k)!=EOF){
for(int i=1;i<=k;i++){
p[i].clear();
}
for(int i=1;i<=n;i++){
scanf("%d%d%d",a+i,v+i,c+i);
p[a[i]].push_back(i);
}
memset(f,0xc0,sizeof(f));
for(int i=0;i<=m;i++)f[0][i]=0;
for(int i=1;i<=k;i++){
for(int u:p[i]){
for(int j=m;j>=v[u];j--){
f[i][j]=max(f[i][j],f[i][j-v[u]]+c[u]);
f[i][j]=max(f[i][j],f[i-1][j-v[u]]+c[u]);//其实不需要这样写
}
}
}
if(f[k][m]>=0)printf("%d\n",f[k][m]);
else puts("Impossible");
}
return 0;
}
Inspiration
没有做出来主要是对背包问题没有完全理解。普通背包问题的状态转移方程分为选与不选两种情况,分组背包将组作为阶段。若能排除不选的情况并按组讨论,这道题就显得简单了。我在讲题时把此题讲得很复杂,其实也是没有完全理解的体现。
HDU5410 CRB and His Birthday
Description
\(n\) 种物品,每种物品无限多。\(m\) 的背包体积。若选择了 \(x\) 件第 \(i\) 种物品,则其价值为 \(a_ix+b_i\),求能够获得的最大价值。
Hint
其实,如果完全理解以上两题(准确地说,只要理解第二题),再结合下面提示,此题就很简单了。
提示: 01 背包 + 完全背包
请思考两分钟。相信你一定想出来了!

浙公网安备 33010602011771号