【NOIP2018】货币系统

本文介绍了一种使用完全背包算法解决数列中元素能否由其他元素组合表示的问题,通过实例解释了如何避免理解错误,展示了正确的解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

考试的时候理解错了题目,以为交易中是可以找零的,第一组样例也说得通(19=10*4-3*7)。

后来发现如果这样的话第二组样例就错了(13=11+19-17),我才看到了题目中的t[i]是非负整数。

于是转换题意,问你一个序列中最多有多少个数能被其他数表示(只能相加,每个数个数无限)。    

显然最小的数无法被表示,于是把数列排序后从最小的数开始做完全背包,每加入一个数就判断一次。

极限时间复杂度O(T*N*A[i])=20*100*25000=50000000,但这是远远达不到的。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int a[105],n,ans;
bool f[25005];
int main()
{
    //freopen("money.in","r",stdin);
    //freopen("money.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        memset(f,0,sizeof(f));
        f[0]=1;
        int mx=a[n];
        for(int i=1;i<=n;i++)
        {
            if(f[a[i]])
                continue;
            ans++;
            for(int j=a[i];j<=mx;j++)
                f[j]|=f[j-a[i]];
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

<think>好的,我现在要解决的是NOIP 2018提高组的货币系统问题。首先,我需要仔细理解题目要求。题目大意是说,给定一个货币系统,包含n种面值的货币,现在需要找到一个等价的简化系统,使得这个简化后的系统能表示的所有金额与原系统完全相同,同时要求简化后的货币数量尽可能少。那么问题的核心应该是如何确定这个最小的货币集合。 根据用户提供的引用资料,尤其是引用[1]和[2],提到了贪心算法和动态规划的应用。但根据引用[2],贪心算法可能在局部最优的情况下无法得到全局最优解,所以可能需要其他方法。比如动态规划,这在引用[3]中提到过,动态规划是解决这类问题的常见方法。 首先,我需要明确题目中的等价条件。原问题中的货币系统(a1, a2, ..., an)和简化后的(b1, b2, ..., bm)等价,当且仅当对于任意非负整数x,x能被原系统表示当且仅当能被简化后的系统表示。那么,简化后的系统必须是原系统的极大线性无关组,也就是说,简化系统中的货币面值不能被其他面值的线性组合(非负整数系数)所表示。 因此,问题转化为找出原系统中那些不能被其他面值组合表示的面值,这些面值必须被保留在简化系统中。而其他可以被组合出来的面值则可以剔除。例如,如果原系统中有面值3和6,那么6可以被3表示,因此6可以被去除。 接下来,我应该如何具体实现这个思路呢?可能的步骤是: 1. 将原货币系统的面值从小到大排序。 2. 对于每个面值a_i,判断是否能用比它小的面值的组合来表示。如果能,则a_i可以剔除;否则必须保留。 3. 最终剩下的未被剔除的面值即为简化系统的元素。 这看起来像是一个完全背包问题。因为对于每个面值a_i,我们需要判断是否存在一种方式,用比它小的面值的组合来组成它。这时候动态规划可能会派上用场。 具体来说,可以用一个布尔数组dp,其中dp[j]表示金额j能否被当前已保留的面值组合表示。初始时,dp[0] = true,因为金额0总是可以被表示。然后,对于每个面值a_i(按升序处理),如果dp[a_i]为false,说明它不能被之前的货币组合表示,因此必须保留,并将其加入结果集中,并更新dp数组,将所有j >= a_i的位置标记为可达到(dp[j] = dp[j] || dp[j - a_i])。如果dp[a_i]已经是true,则说明可以剔除这个面值。 这个过程类似于完全背包的动态规划解法,因为每个面值可以被使用多次。例如,当处理a_i时,如果它已经被保留,那么后续的金额可以通过多次使用a_i来组合出来。 现在,我需要考虑如何实现这个动态规划的过程。可能的步骤是: - 排序数组,以便从小到大处理每个面值。 - 初始化dp数组,大小为最大面值+1,初始时dp[0] = true。 - 遍历每个面值,按升序处理: - 如果当前面值a_i对应的dp[a_i]为false,则保留该面值,并更新dp数组。 - 否则,跳过该面值。 例如,假设原系统是{3, 6, 9},排序后是3,6,9。处理3时,dp[3]初始为false,所以保留3,然后更新dp数组,使得3,6,9等都能被表示。接下来处理6时,检查dp[6]是否为true,此时因为3已经被处理过,所以3+3=6,所以dp[6]已经是true,因此6可以被剔除。同理,9在此时已经被3的组合表示,所以也被剔除。最终简化系统只有3。 另一个例子,比如原系统是{3,5},那么排序后是3,5。处理3时,保留,并更新dp到3,6,9,...。然后处理5时,dp[5]初始为false,所以必须保留。此时,用3和5的组合可以表示更大的数,比如8=3+5,但是单独的3不能表示5,所以5必须保留。最终简化系统是{3,5}。 那这个时候,问题的解法可以总结为:将原数组排序后,逐个检查每个数是否可以被更小的数组成的完全背包所覆盖。如果不能,则保留该数,并更新完全背包的状态。 现在,我需要验证这个思路是否正确。根据NOIP的题目,正确解法应该是这样的。比如,参考货币系统的标准解法,这个方法确实有效。 那么,编写代码的大致步骤是: 1. 输入n和货币数组a。 2. 对a进行排序。 3. 初始化dp数组,dp[0] = true。 4. 结果计数器初始化为0。 5. 遍历每个数a_i: a. 如果dp[a_i]是false,说明必须保留,计数器加一。 b. 然后,对于j从a_i到最大可能值(比如数组中的最大值),更新dp[j] = dp[j] || dp[j - a_i] 6. 最后,计数器即为简化后的货币系统的数量。 不过,这里需要注意的是,dp数组的大小应该如何确定?因为货币的面值可能很大,比如题目中的例子,比如引用[4]中的L可能到1e9,但这里的问题中的货币面值具体范围没有给出。不过根据NOIP的题目限制,可能实际面值的总和不会太大,或者可以通过动态调整来处理。 但直接按照最大面值为数组大小可能不行,比如当原系统中有很大的面值时,数组会很大。不过,在本题中,正确的做法应该是,在动态规划时,每次处理到当前最大的可能值,即当前处理的a_i的最大值。因为当处理到a_i时,所有比它小的数已经被处理过了,所以只需要处理到当前a_i的最大值即可。 或者,可以优化为每次处理到当前已经保留的面值的总和。但这样可能比较复杂。例如,当处理到某个a_i时,最大的可能需要更新的j是当前总可达到的最大值加上a_i。或者,假设原系统的面值都是升序的,那么在处理每个a_i的时候,可以只处理到已存在的最大值加上a_i? 不过,根据正确的解法,正确的做法应该是,无论原面值的大小如何,在判断某个a_i是否需要保留时,只需要判断到a_i本身即可。例如,当处理a_i时,如果在前面的处理中,已经能够组合出a_i,则不需要保留;否则必须保留,并更新后面的dp数组。 这里可能存在一个问题:当处理更大的面值时,前面的小面值已经被处理过,所以此时dp数组在a_i的位置是否已经被前面的面值组合覆盖? 比如,假设原系统是{4,5},排序后是4,5。处理4时,保留,并标记4、8、12等。然后处理5时,检查dp[5]是否为true,此时是否已经被前面的4的组合覆盖?显然没有,因为4只能组成4的倍数,所以5不能被表示,所以必须保留5。这样正确。 另一个例子,原系统是{3,5,9}。排序后3,5,9。处理3时保留,dp数组标记3,6,9,...。处理5时,因为3无法组成5,所以保留5,此时更新后的dp数组可以组成3,5,6(3+3),8(3+5),9(3*3), 10(5+5), etc。处理9时,检查dp[9]是否为true,此时3已经能组成9,所以9被跳过。最终简化系统是3和5。 这样看来,这个算法是正确的。 那么,代码的具体实现需要注意以下几点: - 排序后的数组按升序处理。 - dp数组的大小需要足够大,但由于每个a_i可能较大,比如到1e5或更高,如何处理内存问题? 例如,如果原系统中最大的面值是1e5,那么dp数组需要至少1e5+1的大小。这在内存上是可行的,因为布尔数组每个元素只占1位,或者可以用bitset优化。但实际在编程中,可能用动态数组来处理,例如每次处理到当前的a_i时,最大需要检查到a_i的值。 例如,在代码中,我们可以将dp数组初始化为足够大,或者在处理每个a_i时,动态扩展数组的大小。不过,在编程比赛中,通常会预先估计最大值。例如,本题中的n可能不超过100,而每个面值不超过某个值,比如1e5。所以,可以设置dp的大小为最大的a_i的值加上一些余量,比如1e5+10。 不过,根据题目的实际情况,假设原系统的面值可能很大,但是当处理到某个a_i时,如果前面的面值已经能够组合出a_i,那么后面的更大的面值可能被剔除。但无论如何,必须处理到每个a_i的值,以判断是否需要保留。 因此,在代码中,我们可以先将所有面值排序,然后遍历每个a_i。对于每个a_i,如果当前的dp[a_i]是false,则必须保留,并将该面值加入结果,然后更新dp数组,从a_i到最大的可能值(比如当前最大的已存在的金额加上a_i)进行更新。或者,更简单的是,每次更新到最大值max_val += a_i?或者,可以设置max_val为所有面值的总和,但这可能太大。或者,每次处理a_i时,从a_i到当前的最大可能金额(即之前的max_val + a_i)更新。 不过,实际上,正确的做法应该是,在完全背包问题中,每个物品可以无限使用,所以当处理到a_i时,应该从a_i到某个上限进行遍历,将所有可能的j >=a_i进行更新。但在本题中,处理每个a_i时,只需要判断能否组成这个a_i的值,因此,如果在处理a_i时,dp[a_i]是false,说明必须保留,然后从a_i开始,更新所有可能的j,使得j可以由当前的保留的面值组合而成。 例如,初始时,dp[0] = true。当处理第一个a_i(最小的),此时dp[a_i]是false,所以保留,然后更新所有j >=a_i,使得dp[j] = dp[j] || dp[j -a_i]。这时候,对于每个j,只要j - a_i >=0且dp[j -a_i]为true,那么dp[j]设为true。 这样,随着处理每个保留的面值,dp数组会被逐步填充,能够表示更大的金额。 但问题在于,当原系统的面值很大时,这样的dp数组可能会很大,导致内存或时间问题。例如,若面值是1e5,那么dp数组需要到1e5 * n的大小吗?这显然不可行。 但根据正确的解法,这里的正确性并不受面值大小的影响,而是需要正确的动态规划处理。但如何解决大面值的问题? 例如,假设原系统中有一个非常大的面值,比如1e9,而前面的小面值无法组成它,那么必须保留这个面值。这时,处理到该面值时,需要将dp数组更新到至少1e9,这在内存上是无法实现的。因此,这说明我的思路存在错误,或者题目中的面值其实被限制在一个合理的范围内? 这可能意味着,在题目中,面值的大小可能并不大,或者题目中的解法并不需要处理到面值本身的大小,而是通过数学性质进行优化。 这时候,我需要重新思考问题。或许题目中的货币系统满足一定的条件,例如所有面值都是某个基数的倍数,或者存在某种数学规律,从而可以避免处理过大的dp数组。 或者,正确解法中的动态规划只需要处理到最大的面值即可。例如,当处理到某个a_i时,最大的面值是max_a,那么dp数组只需要处理到max_a即可,因为如果原系统中的所有面值都在max_a以下,那么当处理完所有面值之后,能够组合出的最大金额就是max_a。或者,如果某个更大的金额无法被组合,但题目中只关心能否组合出原系统中的金额? 不,原问题中的等价条件是两个系统能够表示的所有金额完全相同。因此,简化后的系统必须能够表示原系统中的所有金额,并且原系统能表示的所有金额也必须能被简化系统表示。因此,当简化系统中的面值被处理后,必须确保所有原系统中的金额都能被表示。这可能需要处理到原系统中的最大面值。 例如,原系统中的最大面值是max_a,那么在动态规划中,处理到max_a即可,因为更大的金额是否被表示不影响原系统的等价性,但根据题目条件,等价性要求所有金额的表示情况相同,包括超过max_a的部分。这显然无法实现,因为原系统中的面值无法表示超过其组合能力的金额,而简化系统可能无法覆盖这些。 这说明我的思路有问题,或者题目中可能存在隐藏的条件。例如,可能题目中的货币系统是独立的,即其最大面值不能被其他面值组合,因此必须保留,此时动态规划只需要处理到该面值即可? 或者,可能我的解法中的动态规划并不是处理所有可能的金额,而是仅处理到原系统中的最大面值? 或者,可能正确的解法不需要处理到非常大的金额,而是只需要处理到原系统中的最大面值,因为只有原系统中的面值需要被考虑? 这时候,我需要重新理解问题。题目要求简化后的系统与原系统等价,即对于任意非负整数x,两个系统的可表示性相同。因此,假设原系统中的某些面值的组合可以表示非常大的x,但简化系统也必须能表示这些x。所以,如果原系统中的面值组合可以表示某个x,而简化系统无法表示,则不等价。因此,动态规划的处理需要覆盖所有可能的x,这在计算上是不可行的。 这说明我的解法存在问题,或者题目中存在某种数学性质,使得只需要处理到原系统中的最大面值即可? 此时,我需要参考正确的解法。根据网上的资料,正确的解法确实是动态规划,并且只需要处理到最大的面值。例如,对于每个面值a_i,如果它能够被之前的面值组合表示,则可以去除,否则保留。这里的“能否被表示”只需要检查到该面值本身即可,而不需要考虑更大的金额。因此,动态规划的上限是最大的面值。 例如,假设原系统的面值数组排序后是a_1 < a_2 < ... < a_n。最大的面值是a_n。那么,在处理每个a_i时,只需判断是否可以用之前的保留的面值组合出a_i。如果可以,则剔除,否则保留。这时候,动态规划的范围只需要到a_n即可。 因此,在代码中,可以设定dp数组的大小为a_max + 1,其中a_max是原数组中的最大面值。这样,不管面值有多大,只要原数组中的最大值是合理的,比如题目中给定的范围可能不会太大,那么就可以处理。 例如,在NOIP的题目中,可能给定的n比较小,而面值的范围可能也不大,因此这样的动态规划是可行的。 现在,我需要总结正确的解法步骤: 1. 将原数组排序。 2. 初始化dp数组,dp[0] = true,其他为false。 3. 遍历每个面值a_i,按升序处理: a. 如果dp[a_i]为false,说明必须保留该面值,计数器加一。 b. 然后,对于j从a_i到最大的面值(即a_max),更新dp[j] |= dp[j - a_i]。 4. 最终的计数器即为答案。 这样,处理完所有面值之后,剩下的未被剔除的面值即为简化系统,且数量最少。 例如,原题中的样例输入: 假设输入是n=4,面值为3,9,6,5。排序后是3,5,6,9。 处理3时,dp[3]是false,保留。然后更新dp数组,所有3的倍数的位置设为true。 处理5时,dp[5]是false,保留。更新dp数组,现在可以组合出3+5=8,5+5=10,3+3+5=11等。例如,当j从5到9,此时dp[5]变为true,dp[8]变为true,等等。 处理6时,检查dp[6],此时3已经被处理过,3*2=6,所以dp[6]已经是true,因此6被剔除。 处理9时,检查dp[9],此时3*3=9,所以dp[9]已经是true,剔除。 所以最终保留3和5,数量是2。 这样,这个解法是正确的。 现在,我需要考虑如何处理较大的面值。例如,如果最大的面值是1e5,那么dp数组需要开1e5+1的大小,这在内存上是可行的,因为布尔数组的大小是1e5+1,约100KB左右,完全在内存限制内。 因此,这样的解法是可行的。 现在,回到用户的问题,用户希望得到NOIP 2018提高组货币系统题目的解法及解析。根据上述分析,正确的解法是动态规划,判断每个面值是否可被更小的面值组合表示,从而决定是否保留。 现在,我需要将这个思路整理成中文的回答,并按照用户的要求,结构清晰,分步骤说明。同时,引用相关的资料,例如引用[1]中的贪心法可能不适用,而引用[3]中的动态规划方法更合适。 此外,用户要求生成相关问题,我需要根据题目内容,生成相关的三个问题。 最后,需要确保所有数学表达式使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$。</think>### NOIP 2018 提高组 货币系统 解法解析 #### 题目核心 给定一个包含$n$种面值的货币系统,要求找到一个等价的简化系统,使得两个系统能表示的金额集合完全相同,且简化后的系统面值数量最少。 #### 解法思路 1. **问题转化**:简化系统必须是原系统的**极大线性无关组**,即简化系统中的面值无法被其他面值通过非负整数组合表示。 2. **关键步骤**: - 将原面值排序,按升序处理。 - 使用动态规划判断每个面值是否能被更小的面值组合表示。若不能,则必须保留。 #### 算法实现 1. **排序**:将原面值数组按升序排列。 2. **动态规划**: - 定义布尔数组`dp`,其中`dp[i]`表示金额$i$能否被当前已保留的面值组合表示。 - 初始化`dp[0] = true`(金额0始终可表示)。 - 遍历每个面值$a_i$: - 若`dp[a_i]`为`false`,则保留$a_i$,并更新`dp`数组:对$j \geq a_i$,令`dp[j] |= dp[j - a_i]`。 - 若`dp[a_i]`为`true`,则剔除$a_i$。 #### 示例分析 **输入**:面值$\{3, 5, 6, 9\}$ **步骤**: 1. 排序后为$\{3, 5, 6, 9\}$。 2. 处理$3$:`dp[3]`为`false`,保留,更新`dp[3,6,9,...]`。 3. 处理$5$:`dp[5]`为`false`,保留,更新`dp[5,8,10,...]`。 4. 处理$6$:`dp[6]`已为`true`,剔除。 5. 处理$9$:`dp[9]`已为`true`,剔除。 **输出**:简化系统面值数量为$2$($\{3,5\}$)。 #### 复杂度分析 - 时间复杂度:$O(n \cdot \max(a_i))$,其中$\max(a_i)$为最大面值。 - 空间复杂度:$O(\max(a_i))$,取决于`dp`数组大小。 #### 代码实现 ```python def minimal_currency_system(): n = int(input()) a = list(map(int, input().split())) a.sort() max_val = a[-1] dp = [False] * (max_val + 1) dp[0] = True count = 0 for num in a: if not dp[num]: count += 1 for j in range(num, max_val + 1): if dp[j - num]: dp[j] = True print(count) minimal_currency_system() ``` #### 相关引用 贪心法虽高效但可能无法得到全局最优解[^2],而动态规划通过状态转移确保正确性,适用于此类组合优化问题[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值