Files
python/TangDou/AcWing/BeiBao/【总结】方案数-空间至多j.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

279 lines
6.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 背包问题-方案数-空间至多$j$
### 一、$01$背包
例子:给你一堆物品,每个物品有一定的体积,每个物品只能**选一个**,求总体积 <font color='red' size=4><b>不超过$m$</b></font>的 <font color='blue' size=4><b>方案数</b></font>。
<font color='black' size=4><b>求方案数,没有价值累加的概念。</b></font>
输入
``` c++
//物品个数 体积
//每个物品的体积
4 5
2 2 3 7
```
输出
```c++
7
```
**理解一下**
```c++
一个都不选
a[0]
a[1]
a[2]
a[0]+a[1]
a[0]+a[2]
a[1]+a[2]
```
#### 1、二维
```c++
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N][N];//第一维:前i个物品,第二维:体积j及以下
int main() {
scanf("%d %d",&n,&m);
//初始化,在前0个物品中选,所有合法空间,都有一种结果,就是啥也不要,啥也不要也满足“不超过j”这个条件,方案数=1。
for (int i = 0; i <= m; i++) f[0][i] = 1;
for (int i = 1; i <= n; i++) {
int v;
scanf("%d",&v);
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];//不被i件物品
if (j >= v) f[i][j] += f[i - 1][j - v];//能装下就要加上可以转移过来状态的方案数
}
}
printf("%d\n",f[n][m]);
return 0;
}
```
#### 2、一维
```c++
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i <= m; i++) f[i] = 1;
for (int i = 1; i <= n; i++) {
int v;
scanf("%d",&v);
for (int j = m; j >= v; j--) f[j] += f[j - v];
}
printf("%d\n",f[m]);
return 0;
}
```
### 二、完全背包
例子:给你一堆物品,每个物品有一定的体积,每个物品可以选**无数多个**,<font color='red' size=4><b>求总体积不超过$m$</b></font>的 <font color='blue' size=4><b>方案数</b></font>
输入
```c++
3 5
2 3 7
```
输出
```c++
5
```
理解一下:
```c++
一个都不选
a[0]
a[0]*2
a[1]
a[0]+a[1]
```
共$5$种方案。
#### 1、二维
```c++
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N][N];
int main() {
//文件输入
freopen("ZhiDuo_WQ.in", "r", stdin);
scanf("%d %d", &n, &m);
for (int i = 0; i <= m; i++) f[0][i] = 1; //前0种物品中选择,最多不超过m的体积下,选择的方案只有一种合法,就是啥也不选
for (int i = 1; i <= n; i++) {
int v;
scanf("%d", &v);
/*写法1*/
for (int j = 0; j <= m; j++) {
if (j >= v)
f[i][j] = f[i - 1][j] + f[i][j - v];
//注意这里与01背包的差别!!!
// 在01背包的状态转移中,i就是i,它之前不能出现过i,只能从i-1转移
// 在完全背包的状态转移中,i出现过没关系,只要能装下,出现几个都行,可以从i转移
// 01背包:
// f[i][j] = f[i - 1][j] + f[i - 1][j - v];
else
f[i][j] = f[i - 1][j];
}
/*写法2*/
// for (int j = 0; j <= m; j++) {
// f[i][j] = f[i - 1][j];
// if (j >= v) f[i][j] += f[i][j - v];
// }
}
printf("%d\n", f[n][m]);
return 0;
}
```
#### 2、一维
```c++
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i <= m; i++) f[i] = 1;
for (int i = 1; i <= n; i++) {
int v;
scanf("%d",&v);
//完全背包
for (int j = v; j <= m; j++) f[j] += f[j - v];
//对比01背包
//for (int j = m; j >= v; j--) f[j] += f[j - v];
}
printf("%d\n",f[m]);
return 0;
}
```
练习题
[洛谷 P1832](https://www.luogu.com.cn/problem/P1832)
欧筛下标从1开始
```c++
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e3 + 5;
LL f[N][N];
//欧拉筛,下标从1开始
//这个欧拉筛黄海修改过,修改下标从1开始
int primes[N], st[N], cnt;
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[++cnt] = i;
for (int j = 1; primes[j] * i <= n; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int m;
scanf("%d", &m);
get_primes(m); // 1 ~ m以内有cnt个素数
int n = cnt;
for (int i = 0; i <= n; i++) f[i][0] = 1; //初始化,如果刚好只可以选一个数,即j == primes[i],那么只有一种方案。
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
/*
// j足够大,那么对于一个素数可以选,或是不选。两种方案要求总和。
if (j >= primes[i])
f[i][j] = f[i - 1][j] + f[i][j - primes[i]];
else
f[i][j] = f[i - 1][j]; // j不够大,只有不选这一种方案。
*/
//与上面的代码是等价的,一样可以AC
f[i][j] = f[i - 1][j];
if (j >= primes[i]) f[i][j] += f[i][j - primes[i]];
}
}
printf("%lld\n", f[n][m]);
return 0;
}
```
欧筛下标从0开始
```c++
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e3 + 5;
LL f[N][N];
//欧拉筛
int primes[N], st[N], cnt;
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] * i <= n; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int m;
scanf("%d", &m);
get_primes(m); // 1 ~ m以内有cnt个素数
int n = cnt;
for (int i = 0; i <= n; i++) f[i][0] = 1; //初始化,如果刚好只可以选一个数,即j == primes[i],那么只有一种方案。
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
/*
// j足够大,那么对于一个素数可以选,或是不选。两种方案要求总和。
if (j >= primes[i])
f[i][j] = f[i - 1][j] + f[i][j - primes[i]];
else
f[i][j] = f[i - 1][j]; // j不够大,只有不选这一种方案。
*/
//与上面的代码是等价的,一样可以AC
f[i][j] = f[i - 1][j];
if (j >= primes[i - 1]) f[i][j] += f[i][j - primes[i - 1]];
}
}
printf("%lld\n", f[n][m]);
return 0;
}
```