【USACO】麦香牛肉

        

题目描述

农夫约翰的奶牛几乎要武装暴动,因为他们听说麦当劳要推出新产品麦香牛肉。奶牛们要尽力阻止这种产品的上市。他们研究了一种“劣等包装”策略。奶牛们说:“如果麦香牛肉有3块,6块以及10块装这三种,那么想买 1, 2, 4, 5, 7, 8, 11, 14, 或17块牛肉的顾客就得不到满足了。劣等的包装,劣等的产品!” 帮助奶牛们。给出N (不同包装的种类数, 1 <= N <= 10),以及N个正整数 (1 <= i <= 256)表示每种包装中牛肉数量,输出最大的不能买到的牛肉数量。如果任何消费要求都可以被满足或不能满足的牛肉数量没有上界,则输出0。最大的可能值(如果存在)不超过2,000,000,000。

输入

第1行: N 第2..N+1行: 一个盒子里的牛肉数量。

输出

输出题目中要求的单个整数。

样例

输入

3 3 6 10

输出 

17

要解决这个问题,我们需要找到一种方法来确定无法通过任意组合的给定包装数量来满足的最大需求量。这个问题与“Postage Stamp Problem”或“Frobenius Coin Problem”非常相似。对于给定的硬币(或包装)组合,我们需要找到不能表示的最大整数。

思路

  1. 单个包装: 如果只有一种包装,例如 𝑎a,那么所有大于等于 𝑎a 的整数都能表示,所以答案是 𝑎−1a−1。
  2. 两个包装: 如果有两个包装 𝑎a 和 𝑏b,且它们互质(即 gcd⁡(𝑎,𝑏)=1gcd(a,b)=1),则最大的不能表示的数是 𝑎×𝑏−𝑎−𝑏a×b−a−b。
  3. 多个包装: 对于三个或更多个包装组合问题,可以使用动态规划来解决,但这会导致时间复杂度和空间复杂度较高。

然后呢我本人最先想到的方法是动态规划。

来讲一下动态规划:

动态规划(Dynamic Programming, DP)是一种用于解决最优化问题的算法技术。它通过将原问题分解成相互重叠的子问题,避免了重复计算,进而提高计算效率。动态规划通常适用于有重叠子问题和最优子结构性质的问题。

动态规划的核心思想

  1. 重叠子问题:将问题分解成相同子问题的子问题,子问题之间具有重叠。
  2. 最优子结构:问题的最优解可以由其子问题的最优解构成。
  3. 状态转移方程:通过定义状态和状态之间的转移关系来解决问题。

动态规划的基本步骤

  1. 定义状态:明确每一个子问题的状态,一般用一个数组或矩阵来表示。
  2. 确定状态转移方程:找到状态之间的关系,并写出转移方程。
  3. 初始化状态:设置初始条件,通常是最小子问题的解。
  4. 计算并填表:根据状态转移方程递推计算,填满表格或数组。
  5. 返回结果:最后结果往往是表格或数组中的某个元素,表示原问题的最优解。

动态规划的经典例子

以下是一些常见的动态规划问题示例:

  1. 斐波那契数列:用动态规划求解斐波那契数列避免了递归中的重复计算。
  2. 最长公共子序列(LCS):通过二维数组记录两个字符串的公共子序列长度。
  3. 背包问题:解决在给定容量的情况下如何选择物品以使价值最大化。

代码

大致了解之后可以写出代码:

#include<bits/stdc++.h>
using namespace std;
int gcd(int a, int b) {
    while (b) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}
int find_gcd_of_list(const vector<int>& nums) {
    int g = nums[0];
    for (int num : nums) {
        g = gcd(g, num);
    }
    return g;
}
int max_unattainable_quantity(int N, const vector<int>& packages) {
    int g = find_gcd_of_list(packages);
    if (g != 1) {
        return 0;
    }
    int max_value = 2000000;
    vector<bool> can_attain(max_value + 1, false);
    can_attain[0] = true;
    for (int i = 0; i <= max_value; ++i) {
        if (can_attain[i]) {
            for (int p : packages) {
                if (i + p <= max_value) {
                    can_attain[i + p] = true;
                }
            }
        }
    }
    for (int i = max_value; i >= 0; --i) {
        if (!can_attain[i]) {
            return i;
        }
    }
    return 0;
}

int main() {
    int N;
    cin >> N;
    vector<int> packages(N);
    for (int i = 0; i < N; ++i) {
        cin >> packages[i];
    }
    cout << max_unattainable_quantity(N, packages) << endl;
    return 0;
}

然后运行,嘿嘿你会发现跳了几下。很明显会时间超限。

那么怎么不让代码时间超限呢?

优化一下就好了。

优化

对于优化。

我们可以cin,cout换成别的。

但是啊,我们这里不需要。

为了优化上面C++代码,我们可以考虑以下几点:

  1. 使用更合理的上限:虽然题目中最大可能值是 2,000,000,000,但我们可以根据输入的最大公约数和包装数量的最大值来动态调整上限。
  2. 使用集合(set)来优化访问:可以用集合来记录已经可以达到的数量,这样可以减少空间占用,同时加快查询速度。

这样一个速度炒鸡快的代码问世了。

#include<bits/stdc++.h>
using namespace std;
int gcd(int a, int b) {
    while (b) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}
int find_gcd_of_list(const vector<int>& nums) {
    int g = nums[0];
    for (int num : nums) {
        g = gcd(g, num);
    }
    return g;
}
int max_unattainable_quantity(int N, const vector<int>& packages) {
    int g = find_gcd_of_list(packages);
    if (g != 1) {
        return 0;
    }
    set<int> attainable;
    attainable.insert(0);
    int max_package = *max_element(packages.begin(), packages.end());
    int limit = max_package * (N + 1); 
    for (int i = 0; i <= limit; ++i) { 
        if (attainable.count(i)) {
            for (int p : packages) {
                attainable.insert(i + p);
            }
        }
    }
    for (int i = limit; i >= 0; --i) {
        if (!attainable.count(i)) {
            return i;
        }
    }

    return 0;
}

int main() {
    int N;
    cin >> N;
    vector<int> packages(N);
    for (int i = 0; i < N; ++i) {
        cin >> packages[i];
    }
    cout << max_unattainable_quantity(N, packages) << endl;
    return 0;
}

这个代码应该可以AC吧。

完结!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值