背包问题
01背包
用子问题来定义状态
- 设计状态: f[i][j]表示仅考虑前 i 件物品,放入一个容量为 j 的背包可以获得的最大价值。
- 状态转移方程:f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])
表示第i件物品放与不放 - 结果:如果设计状态为至多j,结果为f[N][V],恰好为j,结果为max(f[N][i])
- 滚动数组优化后:
for(int i=0;i<=v;i++) f[i]=0;
for(i=1;i<=N;i++)
for(int j=V;j>=0;j--)
{ if(j-w[i]>0)
f[j]=max(f[j],f[j-w[i]]+v[i])
}
ans=f[V]
完全背包
特点:每种物品有无数件,可以选择 0 或多件
- 0-1背包时,因为要保证第 i 次循环中的状态 f[i][j] 是从状态 f[i-1][j-w[i]]递推而来,为了保证“物品只选一次”而逆序。即“选入第 i 件物品”的策略时,依据的是一个绝无已经选入第 i 件物品的子结果 f[i-1][j-w[i]]
- 现在完全背包考虑“加选一个物品”时,正需要一个可能已选入第 i 种物品的子结果 f[i][j-w[i]],所以必需采用正序循环。
for(int j=0;j<=v;j++) f[j]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=v;j++)
if(j-w[i]>=0)
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
ans=f[V]
多重背包
特点:每种物品有 有限件Ci
解决:采用进制的思想将 Ci 进行二进制拆分,然后转换成 0-1背包问题
保证:拆分后的组合进行选、不选的 0-1 背包的决策,就能涵盖所有 0~Ci 中
的决策。从而转换成了 0-1 背包问题
- 二进制拆分后 N->cnt
int cnt=0;
for(int i=1;i<=n;i++)
{
int t=c[i];
for(int k=1;k<=t;k<<=1)
{
cnt++;
vv[cnt]=k*v[i];ww[cnt]=k*w[i];
t=t-k;
}
if(t>0)
{
cnt++;
vv[cnt]=t*v[i];ww[cnt]=t*w[i];
}
}
for(int i=0;i<=v;i++) f[i]=0;
for(int i=1;i<=cnt;i++)
{
for(int j=v;j>=0;j--)
{
if(j-ww[i]>=0)
f[j]=max(f[j],f[j-ww[i]]+vv[i]);
}
}
题目一东东与ATM
题意
一家银行计划安装一台用于提取现金的机器。
机器能够按要求的现金量发送适当的账单。
机器使用正好N种不同的面额钞票,例如D_k,k = 1,2,…,N,并且对于每种面额D_k,机器都有n_k张钞票。
例如,
N = 3,
n_1 = 10,D_1 = 100,
n_2 = 4,D_2 = 50,
n_3 = 5,D_3 = 10
表示机器有10张面额为100的钞票、4张面额为50的钞票、5张面额为10的钞票。
东东在写一个 ATM 的程序,可根据具体金额请求机器交付现金。
注意,这个程序计算程序得出的最大现金少于或等于可以根据设备的可用票据供应有效交付的现金。
输入
程序输入来自标准输入。 输入中的每个数据集代表特定交易,其格式为:Cash N n1 D1 n2 D2 … nN DN其中0 <= Cash <= 100000是所请求的现金量,0 <= N <= 10是 纸币面额的数量,0 <= nk <= 1000是Dk面额的可用纸币的数量,1 <= Dk <= 1000,k = 1,N。 输入中的数字之间可以自由出现空格。 输入数据正确
输出
对于每组数据,程序将在下一行中将结果打印到单独一行上的标准输出中。
代码
#include<iostream>
using namespace std;
int c[15];int w[15];int f[100010];
int main()
{
int v;
while(cin>>v)
{ int n;cin>>n;
for(int i=1;i<=n;i++)
cin>>c[i]>>w[i];
int cnt=0;int ww[10020];
for(int i=1;i<=n;i++)
{
int t=c[i];
for(int k=1;k<=t;k<<=1)
{
cnt++;
ww[cnt]=k*w[i];
t=t-k;
}
if(t>0)
{
cnt++;
ww[cnt]=t*w[i];
}
}
for(int i=0;i<=v;i++) f[i]=0;
for(int i=1;i<=cnt;i++)
{
for(int j=v;j>=0;j--)
{
if(j-ww[i]>=0)
f[j]=max(f[j],f[j-ww[i]]+ww[i]);
}
}
cout<<f[v]<<endl;
}return 0;
}
题目二 东东开车了
题意
东东开车出去泡妞(在梦中),车内提供了 n 张CD唱片,已知东东开车的时间是 n 分钟,他该如何去选择唱片去消磨这无聊的时间呢
假设:
CD数量不超过20张
没有一张CD唱片超过 N 分钟
每张唱片只能听一次
唱片的播放长度为整数
N 也是整数
我们需要找到最能消磨时间的唱片数量,并按使用顺序输出答案(必须是听完唱片,不能有唱片没听完却到了下车时间的情况发生)
输入
多组输入
每行输入第一个数字N, 代表总时间,第二个数字 M 代表有 M 张唱片,后面紧跟 M 个数字,代表每张唱片的时长 例如样例一: N=5, M=3, 第一张唱片为 1 分钟, 第二张唱片 3 分钟, 第三张 4 分钟
所有数据均满足以下条件:
N≤10000
M≤20
输出
输出所有唱片的时长和总时长,具体输出格式见样例
代码
#include<iostream>
#include<cstring>
using namespace std;
int f[10010];int w[25];
int main()
{
int v;
while(cin>>v)
{
int n;cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];int path[25][10010];memset(path,0,sizeof(path));
for(int j=0;j<=v;j++) f[j]=0;
int sel[25];memset(sel,0,sizeof(sel));
for(int i=1;i<=n;i++)
{
for(int j=v;j>=0;j--)
{
if(j-w[i]>=0)
{ if(f[j]<f[j-w[i]]+w[i])
path[i][j]=1;
f[j]=max(f[j],f[j-w[i]]+w[i]);
}
}
} int k=0;
for(int j=v,i=n;j>=0&&i>=0;i--)
{
if(path[i][j])
{ sel[k++]=w[i];
j=j-w[i];
}
}
for(int i=k-1;i>=0;i--)
cout<<sel[i]<<" ";
cout<<"sum:"<<f[v]<<endl;
}
}