dp背包问题练习题

dp背包问题练习题

货币系统(完全背包)

tips:

  1. 要将$f[0][0]$ 等特殊的点进行初始化操作
  2. 要将$f[N][N] $设置为 $long long$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//从前v种货币中选,凑出N元钱的方案的集合的长度
//第v种货币不选或者选一个/两个/...
//如果第v种货币选一个的方案数相当于从前v-1种货币中选凑出N-v[i]的方案数
#include <bits/stdc++.h>

using namespace std;

const int N = 1e4 + 10;
typedef long long LL;

LL n,m;
LL v[N];
LL f[N][N];

int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> v[i];
}
//注意边界值要记得初始化
f[0][0] = 1;

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*v[i]<=j;k++)
// f[i][j] += f[i-1][j-k*v[i]];
f[i][j] = f[i-1][j];
if(j >= v[i]) f[i][j] += f[i][j - v[i]];
}
}
cout << f[n][m];
}

包子凑数

问题简化为:输入n个数,求整数域内共有多少个数无法被这n个数通过加法来表示;

tips:

  1. 要将$f[0][0]$ 等特殊的点进行初始化操作
  2. 输入n个数,求整数域内共有多少个数无法被这n个数通过加法来表示;
  3. 当这n个数最大公因数等于 1 的时候,个数有限
  4. 最大公因数大于 1 的时候,个数无限
  5. 最大不能表示出来的数必定有个上界; 当两个数a,b(当gcd=1时),最大不能表示的数为(a-1)(b-1)-1 ; 当数字更多的时候,这个上界必然更小(可选的数字变多了); 而99和98是100内最大的互质的数,所以这个上界选择10000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//f[i][j]: 从前 i 种蒸笼种选,使得这若干笼中恰好一共有 j 个包子的方案集合
//属性:集合是否非空
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;

int n,a[110],f[N][N];
int d; //所给的几个数的最大公因数
//输入n个数,求整数域内共有多少个数无法被这n个数通过加法来表示;
//最大公因数等于 1 的时候,个数有限
//最大公因数大于 1 的时候,个数无限

int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
d = gcd(d,a[i]); //0和a[i]的最大公因数为 a[i]
}

f[0][0] = 1;
for(int i=1;i<=n;i++){
for(int j=0;j<=1e4;j++){
f[i][j] = f[i-1][j];
if(j >= a[i]) f[i][j] = max(f[i][j],f[i][j-a[i]]);
}
}
int cnt = 0;
//最大不能表示出来的数必定有个上界
//当两个数a,b(当gcd=1时),最大不能表示的数为(a-1)(b-1)-1
//当数字更多的时候,这个上界必然更小(可选的数字变多了)
//而99和98是100内最大的互质的数,所以这个上界选择10000
for(int i=1;i<1e4;i++){
//方案数为0 代表其不可表示
if(f[n][i] == 0) cnt++;
}
//最大公因数大于 1 的时候,个数无限
if(d > 1){
cout << "INF";
return 0;
}
cout << cnt;
return 0;
}

2022

从2022个物品中选择10个物品,并且物品总体积为2022的方案数

tips:

  1. 从 0到N 初始化 $f[i][0][0]$
    1
    2
    3
    for(int i=0;i<=N;i++){
    f[i][0][0] = 1;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
const int N = 2030;
typedef long long LL;
//从2022个物品中选择10个物品,并且物品总体积为2022
LL f[N][15][N]; //f[i][j][k]表示从前i个物品中选择j个物品,物体的总体积为k

int main()
{
int a[N];
for(int i=0;i<=N;i++){
a[i] = i;
f[i][0][0] = 1;
}
for(int i=1;i<=2022;i++){
for(int j=1;j<=10;j++){
for(int k=1;k<=2022;k++){
//如果没有选择第i个物品,那么当前的方案数就是前i-1个物品中选j
f[i][j][k] = f[i-1][j][k];
if(k >= a[i]) f[i][j][k] += f[i-1][j-1][k-a[i]];
}
}
}
cout << f[2022][10][2022];
return 0;
}

砝码称重(0-1背包)

从前i个砝码中选,重量为 j 的方案数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//0-1背包
//从前i个砝码中选,重量为 j 的方案数
#include <iostream>

using namespace std;

const int N = 110;
const int M = 1e5+10;

int w[N];
int n,sum;
//如果数据只有一个砝码,重量是1e5,那么当j=1e5,转移时的f[i][j+w[i]]就越界了,所以开了两倍
int f[N][2*M];

int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> w[i];
sum+=w[i];
}
//初始化
f[0][0] = 1;
for(int i=1;i<=n;i++){
for(int j=0;j<=sum;j++){
// bool f[N][2*M]
// f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];
f[i][j] = f[i-1][j];
f[i][j] += f[i-1][abs(j-w[i])]; //由于这里加了abs,所以不需要再判断j>=w[i]了
f[i][j] += f[i-1][j+w[i]];
}
}
int cnt=0;
for(int i=1;i<=sum;i++){
if(f[n][i]) cnt++;
}
cout << cnt;
return 0;
}

279. 完全平方数

279. 完全平方数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int f[110][10010]; //d[i][j] 从前i个中选,数量不限制,背包容量等于j的情况下,选择的数量的最小值
int numSquares(int n) {
memset(f,0x3f,sizeof(f)); //由于求最小值min,因此要将所有值初始化为最大值
f[0][0] = 0; // 由于循环里有f[i-1],因此i要从1开始循环,所以f[0][0]单独放到循环外面初始化
for(int i=1;i*i<=n;i++){
f[i][0] = 0; //初始化为0
for(int j=0;j<=n;j++){
f[i][j] = f[i-1][j];
if(j >= i*i) f[i][j] = min(f[i][j],f[i][j-i*i]+1);
}
}
int m = pow(n,1.0/2); //m为背包数量
return f[m][n];
}
};

0-1背包练习题

一个大集合中要分出 两个集合

416. 分割等和子集

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

最后一块石头的重量 II

尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。


dp背包问题练习题
https://cs-lb.github.io/2024/04/07/dp/dp背包问题练习题/
作者
Liu Bo
发布于
2024年4月7日
许可协议