01背包
01背包的基本模型: n个物品 第i个物品的体积为vi 价值为wi 有一个容积为m的背包 要求选择一次额物品进入背包 使得不超过背包容量的物品总价值最大
01背包明显的特点就是一个物品只能选择一次
我们可以用已经处理了的物品数目和当前的的体积作为阶段 f[i,j]表示前i个物品中体积为j的最大价值的选法的价值 那么有f[i,j]=max{f[i-1,j],f[i-1,j-vi]+wi} 初始状态f[0,0]=0 其余初始化为负无穷 因为目标是最大价值 目标:max{f[n][m]};
代码实现:
代码中所有的&1 加上就是滚动数组的做法 节省空间 去掉的话就是正常做法
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=105,M=1e3+5;
int f[2][M],w[N],v[N]; //w价值 v代价
int n,m;
int main() {
memset(f,0xcf,sizeof f);
f[0][0]=0;
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i&1][j]=f[(i-1)&1][j];
}
for(int j=v[i];j<=m;j++){
f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);
}
}
int ans=0;
for(int j=0;j<=m;j++)
ans=max(ans,f[n&1][j]);
cout<<ans;
return 0;
}
每个阶段开始的时候我们都进行了一次f[i-1][]和f[i][]的拷贝操作 我们可以省略掉第一维数组 只用一维数组f[j]表示背包放入总体积为j的物品的最大价值和;
这里一定要使用倒序循环才能保证每个最多被选择一次
因为如果正序 那么中途部分值会被更新影响 导致一个选择多次 变成完全背包 但是倒序的话每次更新用的都是原始数据 而不是更新数据;
cpp
int f[N];
memset(f,0xcf,sizeof size);
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
int ans=0;
for(int j=0;j<=m;j++){
ans=max(ans,f[j]);
}
}
完全背包问题
完全背包在01背包的基础上 每个可以选择多次
代码实现只需要改为正序即可
cpp
int f[N];
memset(f,0xcf,sizeof size);
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
int ans=0;
for(int j=0;j<=m;j++){
ans=max(ans,f[j]);
}
}
P10955 正整数拆分
时间限制: 1.00s 内存限制: 512.00MB
复制 Markdown 退出 IDE 模式
题目描述
给定一个正整数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。
注意:
- 拆分方案不考虑顺序;
- 至少拆分成 2 个数的和。
求拆分的方案数 mod2147483648 的结果。
输入格式
一个正整数 N。
输出格式
输出一个整数,表示结果。
输入输出样例
输入 #1复制运行
7
输出 #1复制运行
14
说明/提示
1≤N≤4000
cpp
#include <bits/stdc++.h>
using namespace std;
int n;
const int N=4e3+1,mod =2147483648;
long long f[N];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
memset(f,0,sizeof f);
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
f[j]=(f[j]+f[j-i])%mod;
}
}
cout<<(f[n]>0?f[n]-1:2147483647)<<'\n';
return 0;
}
多重背包问题
多重背包与01背包不同的是 每一种物品可能有多个
我们有几种做法
1.直接拆分法
将同一种物品 看成独立的物品 当成01背包来做 但是这样很明显效率比较低下
cpp
#include <bits/stdc++.h>
using namespace std;
int n,m;
const int N=4e3+1,mod =2147483648;
long long f[N],c[N],v[N],w[N]; //c表示个数
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i]>>c[i];
}
memset(f,0xcf,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=c[i];j++)
for(int k=m;k>=v[i];k--)
f[k]=max(f[k],f[k-v[i]]+w[i]);
long long ans=0;
for(int i=1;i<=m;i++)ans=max(ans,f[i]);
return 0;
}
2.二进制拆分法
3.单调队列
P10973 Coins
时间限制: 1.00s 内存限制: 512.00MB
复制 Markdown 退出 IDE 模式
题目描述
银国的人们使用硬币。他们有面值分别为 A1,A2,A3,...,An 的硬币。有一天,托尼打开了他的储蓄罐,发现里面有一些硬币。他决定去附近的商店购买一块非常漂亮的手表。他想要支付准确的价格(不找零),而他知道手表的价格不会超过 m 。但他不知道手表的确切价格。
你需要编写一个程序,读取 n、m、A1,A2,A3,...,An 以及对应的数量 C1,C2,C3,...,Cn (表示托尼拥有的每种面值的硬币数量),然后计算托尼可以用这些硬币支付的价格数量(从 1 到 m 的所有价格)。
输入格式
输入包含多个测试用例(不超过 25 组)。每个测试用例的第一行包含两个整数 n(1≤n≤100) 和 m(m≤100000)。第二行包含 2n 个整数,分别表示 A1,A2,A3,...,An 和 C1,C2,C3,...,Cn(1≤Ai≤100000,1≤Ci≤1000)。最后一个测试用例以两个零结尾。
输出格式
对于每个测试用例,在单独的一行输出答案。
输入输出样例
输入 #1复制运行
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
输出 #1复制运行
8
4
我们可以发现若前i中硬币能拼成面值j 只有两类情况
我们设置f[i]表示i是否可以被形成
1.前i-1中硬币能形成面值j 那么i阶段开始 f[j]已经变成true
2.使用了第i中硬币即在i的递推中f[j-a[i]] 为true从而f[j]变为true
我们可以考虑一种贪心策略 设d[j]表示f[j]在阶段i时为true至少要用多少枚第i中硬币 并且尽可能选择情况1 也就是f[j-a[i]] 为true时 如果f[j]已经变为true 则不执行dp的转移 并令d[j]=0; 否则才执行f[j]=f[j]orf[j-a[i]]的转移 并令used[j]=used[j-a[i]] +1;
cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[100010],d[100010],a[110],c[110],n,m,x,y,ans;
int main()
{
while(cin>>n>>m&&n!=0)
{
for(int i=1;i<=m;i++) f[i]=0;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>c[i];
f[0]=1;
for(int i=1;i<=n;i++)
{
x=a[i],y=c[i];
for(int j=0;j<=m;j++) d[j]=0;
for(int j=0;j<=m-x;j++)
if(f[j]==1&&d[j]<y&&f[j+x]==0)
f[j+x]=1,d[j+x]=d[j]+1;
}
ans=0;
for(int i=1;i<=m;i++)
if(f[i]==1) ans++;
cout<<ans<<endl;
}
}
分组背包
基本模型:给定n组物品 第i组有ci个物品 第i组的第j个物品的体积为vij 价值为wij 有一个容积为m的背包 要求选择若干个物品放入背包,使得每组之多选择一个物品且物品总体积不超过m的前提下 物品的价值总和最大
状态转移方程:
f[i,j]=max{f[i-1,j],max{f[i-1,j-vjk]+wik}}
类似于前面几个背包模型 我们可以省略第一维度 然后j倒序 把k的循环放在j的内层 这样是分组背包 否则会变成多重背包
cpp
memset(f,0xcf,sizeof f)
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=1;k<=c[i];k++){
if(j>v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}