背包问题
一、01背包
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益,每种物品至多能用一次,问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行两个整数 v i , w i v_i,w_i vi,wi。
输出格式
一个整数,表示答案。
2.分析
f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个物品,总体积为 j j j 时的最大收益。
转移 f [ i ] [ j ] f[i][j] f[i][j] =max( f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j], f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i−1][j−vi]+ w i w_i wi)。
考虑前 i i i 个物品的状态只和前 i − 1 i-1 i−1 个物品的状态有关,前面 i − 2 i-2 i−2 行都不用考虑。
转移 f [ i ] f[i] f[i]=max( f [ i ] f[i] f[i], f [ i − v j f[i-v_j f[i−vj+ w j w_j wj),但是要倒序循环。
3.代码
#include <iostream>
using namespace std;
int n, m;
int v[1005], w[1005];
int f[1005];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
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]);
cout << f[m];
}
二、完全背包
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益,每种物品能用无限多次,问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行两个整数 v i , w i v_i,w_i vi,wi。
输出格式
一个整数,表示答案。
2.分析
状态转移: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − v i ] + w i f[i][j]=max(f[i-1][j],f[i][j-v_i]+w_i f[i][j]=max(f[i−1][j],f[i][j−vi]+wi。
然后压缩成一维:转移 f [ i ] f[i] f[i]=max( f [ i ] f[i] f[i], f [ i − v j f[i-v_j f[i−vj+ w j w_j wj),但是要正序循环。
3.代码
#include <iostream>
using namespace std;
int n, m;
int v[1005], w[1005];
int f[1005];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
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]);
cout << f[m];
}
三、多重背包1
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益,一共有 l i l_i li 个。问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行三个整数 v i , w i , l i v_i,w_i,l_i vi,wi,li。
输出格式
一个整数,表示答案。
2.分析
最简单的方法就是拆分成 1 1 1 个 1 1 1 个的,然后用 01 01 01 背包。这个就不赘述了。
前置知识:二进制拆分。从 1 , 2 , 3 , . . . , 2 m 1,2,3,...,2^m 1,2,3,...,2m中选一些数字相加,可以得出任意[0,2^{m+1}]的值,每个数字只能用一次。可以用归纳法证明。对于在 [ 2 m + 1 , n ] [2^{m+1},n] [2m+1,n] 内的值,我们取 n − 2 m + 1 n-2^{m}+1 n−2m+1 后,剩下的数字在 [2{m+1}-n-1,2{m+1}] 内,可以从 1 , 2 , . . . , 2 m 1,2,...,2^m 1,2,...,2m中选一些数字相加得到。
3.代码
#include <iostream>
using namespace std;
int n, m, f[2001], v[2001], w[2001], l[2001];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i] >> l[i];
for (int i = 1; i <= n; i++)
{
int res = l[i];
for (int k = 1; k <= res; res -= k, k *= 2)
for (int j = m; j >= v[i] * k; j--)
f[j] = max(f[j], f[j - v[i] * k] + w[i] * k);
for (int j = m; j >= v[i] * res; j--)
f[j] = max(f[j], f[j - v[i] * res] + w[i] * res);
}
cout << f[m];
}
四、分组背包
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m。第 i i i 个物品属于第 a i a_i ai 组,每组物品我们只能从中选择一个。第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益。问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行三个整数 a i , v i , w i a_i,v_i,w_i ai,vi,wi。
输出格式
一个整数,表示答案。
2.分析
把考虑前 i i i 组物品以后,总体积为 0 , 1 , . . . , m 0,1,...,m 0,1,...,m时的最大收益都记下来。
考虑前
i
i
i 组物品,总体积为
j
j
j 时分为两种情况:
1.第
i
i
i 组物品一个没取,问题变成了考虑前
i
−
1
i-1
i−1 组物品,总体积为
j
j
j 时的情况;
2.第
i
i
i 组物品取了,我们枚举了其中的物品
k
k
k,问题变成了考虑前
i
−
1
i-1
i−1 组物品,总体积为
j
−
v
k
j-v_k
j−vk 时的情况;
状态:用 f [ i ] [ j ] f[i][j] f[i][j] 表示考虑了前 i i i 组物品,总体积为 j j j 时的最大收益。
转移: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , m a x k ∈ c i ( f [ i − 1 ] [ j − v i ] + w k ) ) f[i][j]=max(f[i-1][j],max_{k∈c_i}(f[i-1][j-v_i]+w_k)) f[i][j]=max(f[i−1][j],maxk∈ci(f[i−1][j−vi]+wk)),其中 c i c_i ci 表示第 i i i 组物品的编号集合。
3.代码
#include <bits/stdc++.h>
using namespace std;
int n, m, v[1001], w[1001], a[1001], f[1001][1001];
vector<int> c[1001];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &a[i], &v[i], &w[i]), c[a[i]].push_back(i);
for (int i = 1; i <= 1000; i++)
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
for (auto k : c[i])
if (v[k] <= j)
f[i][j] = max(f[i][j], f[i - 1][j - v[k]] + w[k]);
}
printf("%d\n", f[1000][m]);
}
五、二维背包
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,我们一共有 k k k 点体力值。第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益,并且消耗我们 t i t_i ti 点体力值,每种物品只能取一次。问如何选择物品,使得在物品的总体积不超过 m m m 并且花费总体力不超过 k k k 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行三个整数 n , m , k n,m,k n,m,k。
接下来 n n n 行,每行三个整数 v i , w i , t i v_i,w_i,t_i vi,wi,ti。
输出格式
一个整数,表示答案。
2.分析
跟01背包一样,变成二维的就行了。
3.分析
#include<bits/stdc++.h>
using namespace std;
int n,m,k,v[1001],w[1001],t[1001],f[1001][1001];
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&v[i],&w[i],&t[i]);
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
for(int x=k;x>=t[i];x--)
f[j][x]=max(f[j][x],f[j-v[i]][x-t[i]]+w[i]);
printf("%d\n",f[m][k]);
}
六、单调队列(方便后面优化)
1.题目
有 n n n 个生物,第 i i i 个生物会在第i到第 a i a_i ai 天出现,它的攻击力为 b i b_i bi。其中对于所有 i i i,满足 a i ≤ a i + 1 a_i≤a_{i+1} ai≤ai+1。请输出每天出现的生物的攻击力的最大值。
输入格式
第一行一个整数 n n n。
接下来 n n n 行,每行两个整数 a i , b i a_i,b_i ai,bi。
输出格式
一共 n n n 行,每行一个数表示答案。
第 i i i 个整数表示第 i i i 天出现的生物的攻击力的最大值。
2.分析
假设第 i i i 天,对于第 j j j 个生物,如果 b j < b i b_j<b_i bj<bi,那么之后第 j j j 个生物都不用考虑了。
我们用一个队列,按编号从小到大的顺序,存一下到目前为止,哪些生物是需要被考虑的。
队列中的生物的攻击力是单调递减的,攻击力最高的生物在队首。
加入一个生物,队列末尾比它攻击力低的生物都不用考虑了。
3.代码
#include <bits/stdc++.h>
using namespace std;
int n, a[100001], b[100001], c[100001][2];//c是队列
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i];
}
int k = 0, l = 1;//l是头指针,k是尾指针
for (int i = 1; i <= n; i++)
{
for (; k >= l && b[i] >= c[k][0]; --k)//如果当前生物攻击力比队尾的高的话,需要出队
;
c[++k][0] = b[i];
c[k][1] = a[i];//这里记录生物能出现到的时间
printf("%d\n"), c[l][0];
for (; k >= l && c[l][1] == i; ++l)//时间到了之后,生物就要离开,需要出队
;
}
}
七、多重背包2
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,第 i i i 种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi 的收益,一共有 l i l_i li 个。问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得最大的收益?请求出最大收益。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 n n n 行,每行三个整数 v i , w i , l i v_i,w_i,l_i vi,wi,li。
输出格式
一个整数,表示答案。
2.分析
把 j j j (总体积)按照 m o d v i mod\ v_i mod vi(物品体积)分类,只有同一类的状态间可以进行转移。
当 v i = 4 v_i = 4 vi=4 时,其中一类为 1 , 5 , 9 , 13... 1,5,9,13... 1,5,9,13... ,因为中间都是差的物品体积的整数倍。
在某一类中下标为 k k k 的位置,可以转移到下标在 [ k + 1 , k + l i ] [k+1,k+l_i] [k+1,k+li]中的位置;
然后每个位置取最大值。
下标 k , x k,x k,x 之间转移的时候,求 m a x ( f [ k ] + ( x − k ) ∗ w i ) max(f[k]+(x-k)*w_i) max(f[k]+(x−k)∗wi),因为 x ∗ w i x*w_i x∗wi是个定值,所以我们将 f [ k ] − k ∗ w i f[k]-k*w_i f[k]−k∗wi 放进单调队列即可。
3.代码
#include <bits/stdc++.h>
using namespace std;
int n, m, f[100001], c[100001][2]; // f[i]表示体积不超过i时的最大价值
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
int v, w, t;
scanf("%d%d%d", &v, &w, &t);
for (int j = 0; j < v; j++) // 循环的是余数
{
int k = 0, l = 1; // k是头指针,l是尾指针
for (int p = j, x = 1; p <= m; p += v, ++x)
{ // x是下标,p是值,用来转移的
int e = f[p] - x * w, r = x + t; // 减去x*w是为了找潜在的最大值,相当于都到了同一起点,然后看哪个比较远,r是能转移到的
for (; k >= l && c[k][0] <= e; --k)
;
c[++k][0] = e;
c[k][1] = r;
f[p] = c[l][0] + x * w;
for (; k >= l && c[l][1] == x; ++l)
;
}
}
}
printf("%d\n", f[m]);
}
八、数字组合
1.题目
给定 N N N 个正整数 A 1 , A 2 , … , A N A_1,A_2,…,A_N A1,A2,…,AN,从中选出若干个数,使它们的和为 M M M,求有多少种选择方案。
输入格式
第一行包含两个整数 N N N 和 M M M。
第二行包含 N N N个整数,表示 A 1 , A 2 , … , A N A_1,A_2,…,A_N A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
2.分析
01背包的改编问题,把每个数看成一个物品, A i A_i Ai看成体积,求出总体积恰好是 M M M的方案数。
f [ i , j ] f[i,j] f[i,j] 表示所有只从前 i i i 个物品中选,且体积恰好是 j j j 的方案数。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
f[0] = 1; // 相当于f[0.0]=1,除此之外其他初值都为0
for (int i = 0; i < n; i++)
{
int v;
cin >> v;
for (int j = m; j >= v; j--)
f[j] += f[j - v];
}
cout << f[m] << endl;
}
九、买书
1.题目
小明手里有
n
n
n 元钱全部用来买书,书的价格为
10
10
10 元,
20
20
20 元,
50
50
50 元,
100
100
100 元。
问小明有多少种买书方案?(每种书可购买多本)
输入格式
一个整数
n
n
n,代表总共钱数。
输出格式
一个整数,代表选择方案种数。
2.分析
多重背包问题的改编,类似上一道题。
f [ i , j ] f[i,j] f[i,j] 表示所有只从前 i i i 个物品中选,且总体积恰好是 j j j 的方案数。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int m;
int v[5] = {0, 10, 20, 50, 100};
int f[5][N];
int main()
{
cin >> m;
f[0][0] = 1;
for (int i = 1; i <= 4;i++)
for (int j = 0; j <= m;j++)
{
f[i][j] = f[i - 1][j];
if(j>=v[i])
f[i][j] += f[i][j - v[i]];
}
cout << f[4][m] << endl;
}
优化后
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int m;
int v[5] = {0, 10, 20, 50, 100};
int f[N];
int main()
{
cin >> m;
f[0] = 1;
for (int i = 1; i <= 4;i++)
for (int j = 0; j <= m;j++)
{
if(j>=v[i])
f[j] += f[j - v[i]];
}
cout << f[m] << endl;
}
十、潜水员
1.题目
潜水员为了潜水要使用特殊的装备。他有一个带 2 2 2 种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
输入格式
第一行有 2 2 2 个整数 m , n m,n m,n。它们表示氧,氮各自需要的量。
第二行为整数 k k k 表示气缸的个数。
此后的 k k k 行,每行包括 a i , b i , c i a_i,b_i,c_i ai,bi,ci, 3 3 3 个整数。这些各自是:第 i i i 个气缸里的氧和氮的容量及气缸重量。
输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
2.分析
f [ i , j , k ] f[i,j,k] f[i,j,k] 表示所有从前 i i i 个物品中选,且氧气含量至少是 j j j,氮气含量至少是 k k k 的所有选法。
三种类型的区别
- 体积最多是 j j j,全部初始化为 0 0 0,体积大于等于 0 0 0
- 体积恰好是 j j j, f [ 0 , 0 ] f[0,0] f[0,0] 是0,其他是极大值,体积大于等于 0 0 0
- 体积至少是 j j j, f [ 0 , 0 ] f[0,0] f[0,0] 是0,其他是极大值,体积没要求
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 22, M = 80;
int n, m, q;
int f[N][M];//f[i][j]表示体积分别至少是i和j
int main()
{
cin >> n >> m >> q;
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
while (q--)
{
int v1, v2, w;
cin >> v1 >> v2 >> w;
for (int j = n; j >= 0; j--)
for (int k = m; k >= 0; k--)
f[j][k] = min(f[j][k], f[max(0, j - v1)][max(0, k - v2)] + w);
}
cout << f[n][m];
}
十一、背包问题求具体方案
1.题目
01背包问题求方案,要求输出字典序最小的
输入格式
第一行两个整数, N , V N,V N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N N N 行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 i i i件物品的体积和价值。
输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围是 1 … N 1…N 1…N。
2.分析
判断出每个物品是否被选。倒推一下,从第一个开始,必须选的一定要选,可选可不选的也要选,因为要满足字典序的要求。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N], v[N], w[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = n; i >= 1; 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]);
}
//f[1][m]存放最大值。
int j = m;
for (int i = 1; i <= n;i++)
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
{
cout << i << " ";
j -= v[i];
}
return 0;
}
十二、机器分配
1.题目
总公司拥有高效设备 M M M 台,准备分给下属的 N N N 个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这 M M M 台设备才能使国家得到的盈利最大?求出最大盈利值。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M M M.
输入格式
第一行有两个数,第一个数是分公司数 N N N,第二个数是设备台数 M M M;
接下来是一个 N ∗ M N*M N∗M 的矩阵,矩阵中的第 i i i 行第 j j j 列的整数表示第 i i i 个公司分配 j j j 台机器时的盈利。
输出格式
第一行输出最大盈利值;
接下 N N N 行,每行有 2 2 2 个数,即分公司编号和该分公司获得设备台数。
2.分析
将每个公司当作一个物品组,按分组背包来做。然后再根据上一题记录一下路径。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 11,M=16;
int n, m;
int f[N][M], w[N][M],way[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n;i++)
for (int j = 1; j <= m;j++)
cin >> w[i][j];
for (int i = 1; i <= n;i++)
for (int j = 0; j <= m;j++)
for (int k = 0; k <= j;k++)
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
cout << f[n][m] << endl;
int j = m;
for (int i = n; i;i--)
for (int k = 0; k <= j;k++)
if(f[i][j]==f[i-1][j-k]+w[i][k])
{
way[i] = k;
j -= k;
break;
}
for (int i = 1; i <= n;i++)
cout << i << ' ' << way[i] << endl;
return 0;
}
十三、金明的预算方案
1.题目
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N N N 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 0 0 个、 1 1 1 个或 2 2 2 个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 N N N 元。于是,他把每件物品规定了一个重要度,分为 5 5 5 等:用整数 1 5 1~5 1 5 表示,第 5 5 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 10 10 元的整数倍)。他希望在不超过 N N N 元(可以等于 N N N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
输入格式
输入文件的第 1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
2.分析
本质还是分组背包的问题,每个主件和它的附件可分成一组。
3.代码
#include <bits/stdc++.h>
using namespace std;
#define v first
#define w second
typedef pair<int, int> PII;
const int N = 70, M = 32010;
int n, m;
int f[M];
PII master[N];
vector<PII> servent[N];
int main()
{
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
int v, w, q;
cin >> v >> w >> q;
if (!q)
master[i] = {v, v * w};
else
servent[q].push_back({v, v * w});
}
for (int i = 1; i <= n; i++)
if (master[i].v)
{
for (int j = m; j >= 0; j--)
{
auto &sv = servent[i];
for (int k = 0; k < 1 << sv.size(); k++)
{
int v = master[i].v, w = master[i].w;
for (int u = 0; u < sv.size(); u++)
if (k >> u & 1)
{
v += sv[u].v;
w += sv[u].w;
}
if (j >= v)
f[j] = max(f[j], f[j - v] + w);
}
}
}
cout << f[m] << endl;
}
十四、货币系统
1.题目
在网友的国度中共有 n n n 种不同面额的货币,第 i i i 种货币的面额为 a [ i ] a[i] a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n n n、面额数组为 a[1…n] 的货币系统记作 ( n , a n,a n,a)。
在一个完善的货币系统中,每一个非负整数的金额 x x x 都应该可以被表示出,即对每一个非负整数 x x x,都存在 n n n 个非负整数 t [ i ] t[i] t[i] 满足 a [ i ] × t [ i ] a[i] \times t[i] a[i]×t[i] 的和为 x x x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x x x 不能被该货币系统表示出。
例如在货币系统 n = 3 n=3 n=3, a = [ 2 , 5 , 9 ] a=[2,5,9] a=[2,5,9] 中,金额 1 , 3 1,3 1,3 就无法被表示出来。
两个货币系统 ( n , a ) (n,a) (n,a) 和 ( m , b ) (m,b) (m,b) 是等价的,当且仅当对于任意非负整数 x x x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 ( m , b ) (m,b) (m,b) ,满足 ( m , b ) (m,b) (m,b) 与原来的货币系统 ( n , a ) (n,a) (n,a) 等价,且 m m m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m m m。
输入格式
输入文件的第一行包含一个整数 T T T,表示数据的组数。
接下来按照如下格式分别给出 T T T 组数据。
每组数据的第一行包含一个正整数 n n n。
接下来一行包含 n n n 个由空格隔开的正整数 a [ i ] a[i] a[i]。
输出格式
输出文件共有 T T T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。
2.分析
性质 1 1 1: a 1 , a 2 , … , a n a_1,a_2,\dots ,a_n a1,a2,…,an 一定都可以被表示出来。
性质 2 2 2:在最优解中, b 1 , b 2 , … b n b_1,b_2,\dots b_n b1,b2,…bn一定都是从 a 1 , a 2 , … a n a_1,a_2,\dots a_n a1,a2,…an 中选择的。浅证一下,某个 b b b 不属于 a a a 序列,但是如果满足要求的话它是可以被 a a a 序列表示出来的,然后将 a a a 序列替换成 b b b 序列的数,所以这个就没必要了。
性质 3 3 3: b 1 , b 2 , … b n b_1,b_2,\dots b_n b1,b2,…bn一定不能被其他的 b b b 表示出来。
先排序,然后从前往后判断,判断 a i a_i ai 能不能被前面的表示出来。可以转换成完全背包,判断是否恰好能组成 a i a_i ai。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 25010;
int n;
int a[N];
int f[M];
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
sort(a, a + n);
int m = a[n - 1];
memset(f, 0, sizeof f);
f[0] = 1;
int res = 0;
for (int i = 0; i < n; i++)
{
if (!f[a[i]])
res++;
for (int j = a[i]; j <= m; j++)
f[j] += f[j - a[i]];
}
cout << res << endl;
}
return 0;
}
十五、混合背包问题
1.题目
有 N N N 种物品和一个容量是 V V V的背包。
物品一共有三类:
- 第一类物品只能用 1 1 1 次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 s i s_i si 次(多重背包);
每种体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
-
s i = − 1 s_i=−1 si=−1 表示第 i i i 种物品只能用 1 1 1 次;
-
s i = 0 s_i=0 si=0表示第 i i i种物品可以用无限次;
-
s i > 0 s_i>0 si>0 表示第 i i i 种物品可以使用 s i s_i si 次;
输出一个整数,表示最大价值
2.分析
看类型,该是哪种就用哪种。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < n;i++)
{
int v, w, s;
cin >> v >> w >> s;
if(s==0)//完全背包
{
for (int j = v; j <= m;j++)
f[j] = max(f[j], f[j - v] + w);
}
else{
if(s==-1)
s = 1;
for (int k = 1; k <= s;k*=2)
{
for (int j = m; j >= k * v;j--)
f[j] = max(f[j], f[j - k * v] + k * w);
s -= k;
}
if(s)
{
for (int j = m; j >= s * v;j--)
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
}
cout << f[m] << endl;
}
十六、有依赖的背包问题
1.题目
有 N N N 个物品和一个容量是 V V V 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如果选择物品 5 5 5,则必须选择物品 1 1 1 和 2 2 2。这是因为 2 2 2 是 5 5 5 的父节点, 1 1 1 是 2 2 2 的父节点。
每件物品的编号是 i i i,体积是 v i v_i vi,价值是 w i w_i wi,依赖的父节点编号是 p i p_i pi。物品的下标范围是 1 … N 1…N 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 N行数据,每行数据表示一个物品。
第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
2.分析
用递归的思想, f [ u , j ] f[u,j] f[u,j] 表示从以 u u u 为根的子树,总体积不超过 j j j 的方案。也用到了树形dp。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N];
int h[N],e[N],ne[N],idx;
int f[N][N];
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u)
{
for (int i = h[u]; ~i;i=ne[i])
{
int son = e[i];
dfs(e[i]);//递归遍历子节点
//分组背包
for (int j = m - v[u]; j >= 0;j--)
for (int k = 0; k <= j;k++)
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
//更新剩下的部分
for (int i = m; i >= v[u];i--)
f[u][i] = f[u][i - v[u]] + w[u];
//不满足条件的
for (int i = 0; i < v[u];i++)
f[u][i] = 0;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
int root;
for (int i = 1; i <= n;i++)
{
int p;
cin >> v[i] >> w[i] >> p;
if(p==-1)
root = i;
else
add(p, i);
}
dfs(root);
cout << f[root][m] << endl;
return 0;
}
十七、背包问题求方案数
1.题目
有 N N N 件物品和一个容量是 V V V的背包。每件物品只能使用一次。
第 i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109 + 7 109+7 109+7的结果。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 vi,wi,用空格隔开,分别表示第 i件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7的结果。
2.分析
用一个 g ( i , j ) g(i,j) g(i,j) 来存一下 f ( i , j ) f(i,j) f(i,j)取到最值的方案数。这里都表示恰好。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010,mod=1e9+7;
int n, m;
int f[N], g[N];
int main()
{
cin >> n >> m;
memset(f, -0x3f, sizeof f);
f[0] = 0;
g[0] = 1;
for (int i = 0; i < n;i++)
{
int v, w;
cin >> v >> w;
for (int j = m; j >= v;j--)
{
int maxv = max(f[j], f[j - v] + w);
int cnt = 0;
if(maxv==f[j])
cnt += g[j];
if(maxv==f[j-v]+w)
cnt += g[j - v];
g[j] = cnt % mod;
f[j] = maxv;
}
}
int res = 0;
for (int i = 0; i <= m;i++)
res = max(res, f[i]);
int cnt = 0;
for (int i = 0; i <= m;i++)
if(res==f[i])
cnt = (cnt + g[i]) % mod;
cout << cnt << endl;
return 0;
}
十八、能量石
1.题目
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N N N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i i i 块能量石需要花费的时间为 S i S_i Si秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i i i 块能量石最初包含 E i E_i Ei 单位的能量,并且每秒将失去 L i L_i Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0 0 0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y,其中 x是组别编号(从 1 开始),y 是可以获得的最大能量值。
2.分析
贪心+DP。选择吃哪些,按什么顺序吃。
E i + E i + 1 − S i ∗ L i + 1 E_i+E_{i+1}-S_i*L_{i+1} Ei+Ei+1−Si∗Li+1 跟 E i + E i + 1 − S i + 1 ∗ L i E_i+E_{i+1}-S_{i+1}*L_i Ei+Ei+1−Si+1∗Li比,假设后面大,可以得到 S i ∗ L i + 1 < S i + 1 ∗ L i S_i*L_{i+1}<S_{i+1}*L_i Si∗Li+1<Si+1∗Li。所以最优解一定按照 S i / L i S_i/L_i Si/Li的比值从小到大来的。
f [ i , j ] f[i,j] f[i,j] 所有只从前 i i i 快能量石选,且体积不超过 j j j 的方案。
3.代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n;
struct Stone{
int s, e, l;
bool operator< (const Stone &W) const
{
return s * W.l < l * W.s;
}
} stone[N];
int f[N];
int main()
{
int T;
cin >> T;
for (int C = 1; C <= T;C++)
{
int m = 0;
cin >> n;
for (int i = 0; i < n;i++)
{
int s, e, l;
cin >> s >> e >> l;
stone[i] = {s, e, l};
m += s;
}
sort(stone, stone + n);
memset(f, -0x3f, sizeof f);
f[0] = 0;
for (int i = 0; i < n;i++)
{
int s = stone[i].s, e = stone[i].e,l = stone[i].l;
for (int j = m; j >= s;j--)
f[j] = max(f[j], f[j - s] + e - (j - s) * l);
}
int res = 0;
for (int i = 0; i <= m;i++)
res = max(res, f[i]);
printf("Case #%d: %d\n", C, res);
}
}