一、01背包问题
题目链接:2. 01背包问题 - AcWing题库
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
8
f[i][j]代表的是前i件物品,总体积不超过j的总价值
将问题的状态转移分为两种:
1)不选第i个物品 f [i] [j] = f [i-1] [j];
2)选第i个物品 f [i] [j] = f [i-1] [j-v[i]] + w [i];
代码部分:
首先是使用二维数组的做法。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005][1005];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])
{
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
printf("%d",f[n][m]);
return 0;
}
此时会发现其实数组中的i维是可以通过改写省去的,就有了如下的一维写法。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
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]);
}
printf("%d",f[m]);
return 0;
}
这里还有几道01背包问题的题目:
P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1048
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目。
接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
输入输出样例
输入 #1复制
70 3 71 100 69 1 1 2输出 #1复制
3说明/提示
【数据范围】
- 对于 30\%30% 的数据,M \le 10M≤10;
- 对于全部的数据,M \le 100M≤100。
【题目来源】
NOIP 2005 普及组第三题
附上AC代码:
#include<bits/stdc++.h>
using namespace std;
long long int t,m;
long long int v[1005],w[1005];
long long int f[1005][1005];
int main()
{
scanf("%lld%lld",&t,&m);
for(int i=1;i<=m;i++)
scanf("%lld%lld",&v[i],&w[i]);
f[0][0]=0;
for(int i=1;i<=m;i++)
for(int j=0;j<=t;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
printf("%lld",f[m][t]);
return 0;
}
P1802 5 倍经验日 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1802
题目背景
现在乐斗有活动了!每打一个人可以获得 5 倍经验!absi2011 却无奈的看着那一些比他等级高的好友,想着能否把他们干掉。干掉能拿不少经验的。
题目描述
现在 absi2011 拿出了 xx 个迷你装药物(嗑药打人可耻…),准备开始与那些人打了。
由于迷你装药物每个只能用一次,所以 absi2011 要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 22 个药去打别人,别人却表明 33 个药才能打过,那么相当于你输了并且这两个属性药浪费了。
现在有 nn 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。
要求求出最大经验 ss,输出 5s5s。
输入格式
第一行两个数,nn 和 xx。
后面 nn 行每行三个数,分别表示失败时获得的经验 \mathit{lose}_ilosei,胜利时获得的经验 \mathit{win}_iwini 和打过要至少使用的药数量 \mathit{use}_iusei。
输出格式
一个整数,最多获得的经验的五倍。
输入输出样例
输入 #1复制
6 8 21 52 1 21 70 5 21 48 2 14 38 3 14 36 1 14 36 2输出 #1复制
1060说明/提示
【Hint】
五倍经验活动的时候,absi2011 总是吃体力药水而不是这种属性药。
【数据范围】
- 对于 10\%10% 的数据,保证 x=0x=0。
- 对于 30\%30% 的数据,保证 0\le n\le 100≤n≤10,0\le x\le 200≤x≤20。
- 对于 60\%60% 的数据,保证 0\le n,x\le 1000≤n,x≤100, 10<lose_i,win_i\le 10010<losei,wini≤100,0\le use_i\le 50≤usei≤5。
- 对于 100\%100% 的数据,保证 0\le n,x\le 10^30≤n,x≤103,0<lose_i\le win_i\le 10^60<losei≤wini≤106,0\le use_i\le 10^30≤usei≤103。
【题目来源】
fight.pet.qq.com
absi2011 授权题目
这道是一道有些许变化的01背包问题,不难,附上AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
long long int n,x;
long long int lose[N],win[N],use[N];
long long int f[N];
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&lose[i],&win[i],&use[i]);
for(int i=1;i<=n;i++)
for(int j=x;j>=0;j--)
{
if(j>=use[i])
f[j]=max(f[j]+lose[i],f[j-use[i]]+win[i]);
else
f[j]=f[j]+lose[i];
}
cout<<5ll*f[x];
return 0;
}
二、完全背包问题
题目链接:3. 完全背包问题 - AcWing题库
有 NN 种物品和一个容量是 VV 的背包,每种物品都有无限件可用。
第 ii 种物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
10
f[i][j]表示从前i个物品里选,总体积不超过j时,最大的价值
将问题的状态转移分为两种:
1)不选第i个物品 f [i] [j] = f [i-1] [j];
2)选不定个第i个物品 f [i] [j] = f [i-1] [j-v [i]] + w [i];
第二种是推导出来的,直接看含义可能看不出来。
原本应该为
f [i] [j] = max( f[i-1] [j] , f[i-1] [j-v[i]] + w[i] , f[i-1] [j-2*v[i]] + 2*w[i] , … ),
此时再写出
f [i] [j-v[i]] = max ( f[i-1] [j-v[i]] , f[i-1] [j-2*v[i]] + w[i] , f[i-1] [j-3*v[i]] + 2*w[i] , … )
因为物品可以选无穷多个,则上面两式有一一对应的关系
所以可以看出上下两式的关系得出:
f [i] [j] = max ( f[ i-1] [j] , f [i] [j-v[i]] + w[i] )
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005];
int f[1005][1005];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])
{
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
}
}
printf("%d",f[n][m]);
return 0;
}
P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1616
题目背景
此题为纪念 LiYuxiang 而生。
题目描述
LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是 LiYuxiang,你能完成这个任务吗?
此题和原题的不同点:
11. 每种草药可以无限制地疯狂采摘。
22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!
输入格式
输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm。
第 22 到第 (m + 1)(m+1) 行,每行两个整数,第 (i + 1)(i+1) 行的整数 a_i, b_iai,bi 分别表示采摘第 ii 种草药的时间和该草药的价值。
输出格式
输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
输入输出样例
输入 #1复制
70 3 71 100 69 1 1 2输出 #1复制
140说明/提示
数据规模与约定
- 对于 30\%30% 的数据,保证 m \le 10^3m≤103 。
- 对于 100\%100% 的数据,保证 1 \leq m \le 10^41≤m≤104,1 \leq t \leq 10^71≤t≤107,且 1 \leq m \times t \leq 10^71≤m×t≤107,1 \leq a_i, b_i \leq 10^41≤ai,bi≤104。
这也是一道完全背包问题,几乎一样的做法,但是记得数组开大一点,不然会RE,附上AC代码:
#include<bits/stdc++.h>
using namespace std;
long long int t,m;
long long int v[10005],w[10005];
long long int f[10000005];
int main()
{
scanf("%lld%lld",&t,&m);
for(int i=1;i<=m;i++)
scanf("%lld%lld",&v[i],&w[i]);
f[0]=0;
for(int i=1;i<=m;i++)
for(int j=v[i];j<=t;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
printf("%lld",f[t]);
return 0;
}
三、多重背包问题(Ⅰ)
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100输入样例
4 5 1 2 3 2 4 1 3 4 3 4 5 2
输出样例:
10
多重背包不像完全背包一样可以无限地选取,所以不能推导出那样的一般式。所以直接从0开始循环到s[i]。
f[i][j]表示从第i个物品里选k个,总体积不超过j时,最大的价值
选取k件第i件物品 f [i] [j] = f [i-1] [j - v[i] * k] + w [i] * k;
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1005],w[1005],s[1005];
int f[1005][1005];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&v[i],&w[i],&s[i]);
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
cout<<f[n][m];
return 0;
}
四、多重背包问题(Ⅱ)
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5 1 2 3 2 4 1 3 4 3 4 5 2
输出样例:
10
如果和Ⅰ一样三重循环,那就是10的9次方,肯定会超时,所以需要优化,这里采用的是二进制优化。将物品个数变成1,2,4……然后可以拼成1~s的任意个数,将多重背包问题转化为01背包问题。
O(n)=N*M*logs;
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=10000005;
int v[N],w[N],s[N];
int f[N];
int main()
{
scanf("%d%d",&n,&m);
int cnt=0;
for(int i=1;i<=n;i++)
{
int a,b,c,k=1;
scanf("%d%d%d",&a,&b,&c);
while(k<=c)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
c-=k;
k*=2;
}
if(c>0)
{
cnt++;
v[cnt]=a*c;
w[cnt]=b*c;
}
}
for(int i=1;i<=cnt;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j-1],f[j-v[i]]+w[i]);
cout<<f[m];
return 0;
}
五、分组背包问题
题目链接:9. 分组背包问题 - AcWing题库
有 NN 组物品和一个容量是 VV 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vijvij,价值是 wijwij,其中 ii 是组号,jj 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 NN 组数据:
- 每组数据第一行有一个整数 SiSi,表示第 ii 个物品组的物品数量;
- 每组数据接下来有 SiSi 行,每行有两个整数 vij,wijvij,wij,用空格隔开,分别表示第 ii 个物品组的第 jj 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100输入样例
3 5 2 1 2 2 4 1 3 4 1 4 5
输出样例:
8
1.第i组物品不选 f[i][j]=f[i-1][j];
2.第i组物品选哪个 f[i][j]=max(f[i][j],f[i-1][j-v[i,k]]+w[i,k]
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N][N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
for(int j=1;j<=s[i];j++)
{
scanf("%d%d",&v[i][j],&w[i][j]);
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
for(int k=1;k<=s[i];k++)
{
if(j>=v[i][k])
f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
cout<<f[n][m];
}
优化成一维数组:
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
for(int j=1;j<=s[i];j++)
{
scanf("%d%d",&v[i][j],&w[i][j]);
}
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=s[i];k++)
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
cout<<f[m];
}