You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 171. 送礼物

一、题目描述

达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]

达达的力气很大,他一次可以搬动 重量之和不超过 W任意多个 物品。

达达希望一次搬掉 尽量重 的一些物品,请你告诉达达在他的力气范围内 一次性能搬动的最大重量 是多少

输入格式 第一行两个整数,分别代表 WN

以后 N 行,每行一个正整数表示 G[i]

输出格式 仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。

数据范围 1≤N≤46 1≤W,G[i]≤2^{31}1

输入样例

20 5
7
5
4
18
1

输出样例

19

二、【经典错误解法】:01背包

本题是不能使用01背包的,原因:

  • 原因1 01背包的时间复杂度:N\times V 本题:N=46, V=2^{31}1 ,所以N*V,上面的至少 2e9 * 46

    C++每秒能算1亿次,100000000=1e801背包肯定TLE

  • 原因2 因为我们需要给结果数组f[N]初始化,这代表的是每一个可能的体积能装的最大重量是多少,而本题的上限2^{31}1实在太大了,C++开不了这么大的数组。

不能AC01背包解法:

#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

#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;
}