|
|
|
|
## [$AcWing$ $171$. 送礼物](https://www.acwing.com/problem/content/description/173/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
达达帮翰翰给女生送礼物,翰翰一共准备了 $N$ 个礼物,其中第 $i$ 个礼物的重量是 $G[i]$。
|
|
|
|
|
|
|
|
|
|
达达的力气很大,他一次可以搬动 **重量之和不超过 $W$** 的 **任意多个** 物品。
|
|
|
|
|
|
|
|
|
|
达达希望一次搬掉 **尽量重** 的一些物品,请你告诉达达在他的力气范围内 **一次性能搬动的最大重量** 是多少
|
|
|
|
|
|
|
|
|
|
**输入格式**
|
|
|
|
|
第一行两个整数,分别代表 $W$ 和 $N$。
|
|
|
|
|
|
|
|
|
|
以后 $N$ 行,每行一个正整数表示 $G[i]$。
|
|
|
|
|
|
|
|
|
|
**输出格式**
|
|
|
|
|
仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。
|
|
|
|
|
|
|
|
|
|
**数据范围**
|
|
|
|
|
$1≤N≤46$
|
|
|
|
|
$1≤W,G[i]≤2^{31}−1$
|
|
|
|
|
|
|
|
|
|
**输入样例**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
20 5
|
|
|
|
|
7
|
|
|
|
|
5
|
|
|
|
|
4
|
|
|
|
|
18
|
|
|
|
|
1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
19
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 二、【经典错误解法】:$01$背包
|
|
|
|
|
本题是不能使用$01$背包的,原因:
|
|
|
|
|
|
|
|
|
|
* **原因$1$**:
|
|
|
|
|
$01$背包的时间复杂度:$N\times V$
|
|
|
|
|
本题:$N=46, V=2^{31}−1$ ,所以$N*V$,上面的至少 $2e9 * 46$
|
|
|
|
|
|
|
|
|
|
$C++$每秒能算$1$亿次,$100000000=1e8$,$01$背包肯定$TLE$
|
|
|
|
|
|
|
|
|
|
* **原因$2$**:
|
|
|
|
|
因为我们需要给结果数组$f[N]$初始化,这代表的是每一个可能的体积能装的最大重量是多少,而本题的上限$2^{31}−1$实在太大了,$C++$开不了这么大的数组。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
不能$AC$的$01$背包解法:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 110;
|
|
|
|
|
|
|
|
|
|
int n, m;
|
|
|
|
|
int w[N];
|
|
|
|
|
int f[N];
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
cin >> m >> n;
|
|
|
|
|
for (int i = 1; i <= n; i++) cin >> w[i];
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
for (int j = m; j >= w[i]; j--)
|
|
|
|
|
f[j] = max(f[j], f[j - w[i]] + w[i]);
|
|
|
|
|
|
|
|
|
|
cout << f[m] << endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 三、题目解析
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
每种物品有选与不选两种情况,共$8\times 1e6$种情况
|
|
|
|
|
$N=46,N/2=23,2^{23} \approx 8 \times 1e6$
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### $Code$
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 48; // 1≤N≤46
|
|
|
|
|
|
|
|
|
|
// 双向DFS
|
|
|
|
|
int n; // n个礼物
|
|
|
|
|
int m; // 重量之和不超过m,上限
|
|
|
|
|
int k; // 前k个,即索引下标0~k-1
|
|
|
|
|
int w[N]; // 每个礼物的重量
|
|
|
|
|
|
|
|
|
|
// 前半部分收集到的所有和,下标因为一直在保持++状态,所以最后一次执行完,也可以理解为前半部分数组的个数
|
|
|
|
|
// 在排序去重后,此变更也可以视为前半段数组的元素个数,在二分中,因为需要使用的是索引号:0~idx-1
|
|
|
|
|
int sum[1 << (N / 2)], idx;
|
|
|
|
|
int ans; // 最大重量
|
|
|
|
|
|
|
|
|
|
// u:第几号礼物,下标从0开始
|
|
|
|
|
// s:本路线上累加的礼物物理和
|
|
|
|
|
void dfs1(int u, int s) {
|
|
|
|
|
if (u == k) { // 如果能够到达第k个下标位置,表示前面0~k-1共k个选择完毕
|
|
|
|
|
sum[idx++] = s; // 记录礼物重量和
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 如果加上u号物品重量,不会超过上限m,那么可以选择
|
|
|
|
|
// 技巧:为防止两个int相加爆int,同时不想使用long long,可以考虑使用减法
|
|
|
|
|
if (s <= m - w[u]) dfs1(u + 1, s + w[u]);
|
|
|
|
|
// 放弃u号物品,走到下一个u+1号面前
|
|
|
|
|
dfs1(u + 1, s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 后半部分
|
|
|
|
|
void dfs2(int u, int s) {
|
|
|
|
|
if (u == n) {
|
|
|
|
|
// 目标:在一个从小到大的有序数组中,找到 小于等于某个数 的最大值
|
|
|
|
|
int l = 0, r = idx; //[左闭右开)
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) >> 1;
|
|
|
|
|
if (sum[mid] > m - s)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
l--; // l是第一个大于目标值的位置,我们要找的是小于等于目标值的位置,l-1就是答案
|
|
|
|
|
|
|
|
|
|
// 更新更大重量
|
|
|
|
|
ans = max(ans, sum[l] + s);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果加上u号物品重量,不会超过上限m,可以选择
|
|
|
|
|
if (s <= m - w[u]) dfs2(u + 1, s + w[u]);
|
|
|
|
|
|
|
|
|
|
// 放弃当前礼物
|
|
|
|
|
dfs2(u + 1, s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
scanf("%d %d", &m, &n); // 先读入m再读入n,别整反了
|
|
|
|
|
for (int i = 0; i < n; i++) scanf("%d", &w[i]); // 每个礼物重量
|
|
|
|
|
|
|
|
|
|
// 由大到小排序,搜索范围会小
|
|
|
|
|
sort(w, w + n, greater<int>());
|
|
|
|
|
// sort(w, w + n), reverse(w, w + n);
|
|
|
|
|
|
|
|
|
|
// 一家一半
|
|
|
|
|
k = n >> 1;
|
|
|
|
|
|
|
|
|
|
// 前面开始搜索 0~k-1
|
|
|
|
|
// dfs1,枚举出了所有可能出现的组合值
|
|
|
|
|
dfs1(0, 0);
|
|
|
|
|
|
|
|
|
|
// 前半部重量累加结果由小到大排序
|
|
|
|
|
sort(sum, sum + idx);
|
|
|
|
|
|
|
|
|
|
// 去重
|
|
|
|
|
idx = unique(sum, sum + idx) - sum;
|
|
|
|
|
|
|
|
|
|
// 后半部分搜索 k~n
|
|
|
|
|
dfs2(k, 0);
|
|
|
|
|
|
|
|
|
|
// 输出答案
|
|
|
|
|
printf("%d\n", ans);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|