这周和上一周着重学习了背包问题,比普通的线性DP状态转移方程难写,也更难理解,但是听人说这东西不能多想,状态转移方程只要没有太大问题,其他交给计算机就行,
一、01背包
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
因为该状态f[i][v]只与上一状态有关,所以出现了简化(一维):f[v]=max{f[v],f[v-c[i]]+w[i]}
还有一点需要逆序!
因为求容量v的状态值时,只与前i-1件物品装入容量小于或等于v的状态有关,与大于v的状态无关。可先更新大容量的状态值。
我自己的理解就是,想求现在v容量i件物品最大价值,而需要比较的是上一状态i-1件物品v容量的价值(i不放背包)和i-1件物品v-c[i]容量的价值(i放入背包)。所以不能先把小容量的状态值修改了,否则会影响这一个状态寻找最大值
题目:Roy想要抢银行,给出每个银行拥有的金钱和被抓的概率,给出Roy最大能承受的被抓概率P,求Roy不被抓的前提下最多偷盗的钱财。可以承受的最大被抓的概率为p,即:如果安全的概率大于1-p则符合要求。
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=10005;
double dp[N];
struct g {
int v;
double c;
} a[N];
int main()
{
int n,t;
double p;
cin >> t;
while(t--)
{
int sum=0;
cin >>p>> n;
for(int i=0; i<n; i++)
{
cin >>a[i].v>>a[i].c;
sum+=a[i].v;
}
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=0; i<n; i++)
for(int j=sum; j>=a[i].v; j--)
{
dp[j]=max(dp[j],dp[j-a[i].v]*(1-a[i].c));
}
for(int i=sum; i>=0; i--)
if(dp[i]>1-p)
{
cout<<i<<endl;
break;
}
}
return 0;
}
作为背包问题中最简单的一类,状态转移方程写起来并不困难,只需要注意写代码时的一些细节即可。
二、完全背包
与01背包不同的地方在于物品有无限件,所以在考虑当前这件物品的个数时需要多一维数组,f[i][v-w[i]]正是我们需要的子问题,这时候的f[i][v]是由f[i][v-w[i]]推来的,所以用正序顺序循环。
f[i][j]=max{f[i-1][j-k*w[i]]+k*v[i]|0=<k<=V/w[i]}
时间优化:
f[i][j]=max(f[i-1][j], f[i][j–wi]+vi)f[ i ][ v ]//为前 i 种物品恰好放入容量为 v 的背包的最大权值。
UVA147 Dollars - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
double a;
int m;
int a_a[]={2000,1000,400,200,100,40,20,10,4,2,1};
long long ans[6001];
int main(){
ans[0]=1;
for(int i=0;i<=10;i++)
for(int j=a_a[i];j<=6000;j++)
ans[j]+=ans[j-a_a[i]];
while(1){
cin>>a;
if(a==0)break;
printf("%6.2lf",a);
m=a*20;
printf("%17lld\n",ans[m]);
}
return 0;
}
这道题不能忘记四舍五入,不然不会对
三、多重背包
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。(在完全背包的基础上,找到每件物品的最优个数从而就找到了最优解)
理解因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
f[i][v]//表示前i种物品恰放入一个容量为v的背包的最大权值,则:
f[i][v]=max{ f[ i-1 ][ v-k*w[i] ] + k*val[ i ] | 0<= k <= n[ i ]}
题目
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
#include <iostream>
using namespace std;
int N,V;
int dp[1010];
int main()
{
scanf("%d%d",&N,&V);
int v,w,s;
for(int i=1;i<=N;i++)
{
scanf("%d%d%d",&v,&w,&s);
for(int j=V;j>=0;j--){
for(int k=1;k<=s&&k*v<=j;k++)
{
dp[j]=max(dp[j],dp[j-k*v]+k*w);
}
}
}
printf("%d",dp[V]);
return 0;
}
四、分组背包
其实类似于01背包,对于一个物品有两种决策选或不选,但是分组背包是在01背包的基础上对物品进行了分组,并且每一组只能最多选择一个物品,所以可以用01背包的思想去思考分组背包.
分析:我们设f[i][j]为当前考虑到了第i组物品,剩余容里为j的背包能装物品的最大价值,那么很容易想到我们需要去枚举第i组物品,考虑选哪一个物品时最优的(或者不选),状态转移方程就是
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}
P1757 通天之分组背包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
int v,n,t;
int x;
int g[205][205];
int i,j,k;
int w[10001],z[10001];
int b[10001];
int dp[10001];
int main(){
cin>>v>>n;
for(i=1;i<=n;i++){
cin>>w[i]>>z[i]>>x;
t=max(t,x);
b[x]++;
g[x][b[x]]=i;
}
for(i=1;i<=t;i++){
for(j=v;j>=0;j--){
for(k=1;k<=b[i];k++){
if(j>=w[g[i][k]]){
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]);
}
}
}
}
cout<<dp[v];
return 0;
}
最近几周忙着复习,所以看的题目不是很多,每种背包就只看了两三道,而且以上的有些代码不是自己写出来的,只能说是搞懂了之后抄上来的,看到题目少导致题目理解不透彻,以这些题目结束这学期的算法课程有些草率了,所以如果可以我希望之后也可继续将算法学习下去。