DP -完全背包 - NOIP2018提高组 - 货币系统

DP -完全背包 - NOIP2018提高组 - 货币系统

1、货币系统(简单)

给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。

输入格式
第一行,包含两个整数n和m。

接下来n行,每行包含一个整数,表示一种货币的面值。

输出格式
共一行,包含一个整数,表示方案数。

数据范围
n≤15,m≤3000

输入样例:
3 10
1
2
5
输出样例:
10

分析:

本 题 与 — — 本题与—— 《买书》 类 似 。 类似。

n 种 货 币 < = > n 种 物 品 组 成 面 值 为 m 的 货 币 < = > 装 满 容 量 为 m 的 背 包 。 n种货币<=>n种物品\\组成面值为m的货币<=>装满容量为m的背包。 n<=>nm<=>m

问 题 即 从 n 种 物 品 中 选 择 , 求 能 够 装 满 背 包 的 方 案 总 数 , 转 化 为 完 全 背 包 问 题 。 问题即从n种物品中选择,求能够装满背包的方案总数,转化为完全背包问题。 n

代码:

#include<iostream>
#include<algorithm>

using namespace std;

const int N=3010;

int n,m;
long long f[N];

int main()
{
    cin>>n>>m;
    
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        int v;
        cin>>v;
        for(int j=v;j<=m;j++)
            f[j]+=f[j-v];
    }
    
    cout<<f[m]<<endl;
    
    return 0;
}

2、货币系统(增强版 - NOIP 2018)

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。

为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。

在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]× t[i] 的和为 x。

然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。

例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。

两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。

他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。

他们希望你来协助完成这个艰巨的任务:找到最小的 m。

输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。

接下来按照如下格式分别给出T组数据。

每组数据的第一行包含一个正整数 n。

接下来一行包含 n 个由空格隔开的正整数 a[i]。

输出格式
输出文件共有T行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

数据范围
1≤n≤100,
1≤a[i]≤25000,
1≤T≤20

输入样例:
2 
4 
3 19 10 6 
5 
11 29 13 19 17 
输出样例:
2
5

分析:

简 单 描 述 题 意 : 给 定 一 个 货 币 系 统 a , 包 含 n 种 货 币 面 额 { a 1 , a 2 , . . . , a n } 。 简单描述题意:\\给定一个货币系统a,包含n种货币面额\{a_1,a_2,...,a_n\}。 an{a1,a2,...,an}

货 币 系 统 b = { b 1 , b 2 , . . . , b m } 与 货 币 系 统 a 等 价 , 当 且 仅 当 系 统 a 能 够 凑 成 的 所 有 面 额 均 能 由 系 统 b 中 的 货 币 凑 出 。 货币系统b=\{b_1,b_2,...,b_m\}与货币系统a等价,当且仅当系统a能够凑成的所有面额均能由系统b中的货币凑出。 b={b1,b2,...,bm}aab

抽 象 地 , 对 表 达 式 ∑ i = 1 n a i t i 所 能 得 到 的 任 意 值 , 均 有 ∑ j = 1 m b j t j 与 之 相 等 , 其 中 t i 与 t j 均 是 大 于 等 于 0 的 正 整 数 。 抽象地,对表达式\sum_{i=1}^{n}a_it_i所能得到的任意值,均有\sum_{j=1}^{m}b_jt_j与之相等,其中t_i与t_j均是大于等于0的正整数。 i=1naitij=1mbjtjtitj0

求 集 合 b 中 元 素 的 最 小 个 数 。 求集合b中元素的最小个数。 b

题 解 : 若 系 统 b 与 系 统 a 等 价 , 且 系 统 b 是 最 优 方 案 , 则 系 统 b 必 然 满 足 下 列 三 条 性 质 : 题解:\\若系统b与系统a等价,且系统b是最优方案,则系统b必然满足下列三条性质: :babb
① 、 a 1 , a 2 , . . . , a n 一 定 可 以 由 系 统 b 表 示 出 来 。 ② 、 b 1 , b 2 , . . . , b m 一 定 能 由 系 统 a 中 的 某 一 个 数 单 独 表 示 出 来 。 ③ 、 b 1 , b 2 , . . . , b m 中 的 任 一 数 值 一 定 不 能 被 其 他 的 b i 表 示 出 来 。 ①、a_1,a_2,...,a_n一定可以由系统b表示出来。\\②、b_1,b_2,...,b_m一定能由系统a中的某一个数单独表示出来。\\③、b_1,b_2,...,b_m中的任一数值一定不能被其他的b_i表示出来。 a1,a2,...,anbb1,b2,...,bmab1,b2,...,bmbi

说 明 性 质 ② : 假 设 b i = a j t j + a k t k , ( t j , t k > = 1 ) , 又 因 为 a j 可 由 某 个 b u 表 示 , a k 可 由 某 个 b v 表 示 , 假 设 a j = b u t u , a k = b v t v , ( t u , t v > = 1 ) , 则 b i = b u t j t u + b v t k t v , 说 明 b i 是 多 余 的 , 可 以 删 除 。 因 此 , 最 优 解 的 系 统 b 中 的 任 何 一 个 元 素 b i , 均 能 由 系 统 a 中 的 某 个 元 素 单 独 表 示 。 说明性质②:假设b_i=a_jt_j+a_kt_k,(t_j,t_k>=1),又因为a_j可由某个b_u表示,a_k可由某个b_v表示,\\假设a_j=b_ut_u,a_k=b_vt_v,(t_u,t_v>=1),则b_i=b_ut_jt_u+b_vt_kt_v,说明b_i是多余的,可以删除。\\因此,最优解的系统b中的任何一个元素b_i,均能由系统a中的某个元素单独表示。 bi=ajtj+aktk,(tj,tk>=1)ajbuakbvaj=butu,ak=bvtv,(tu,tv>=1)bi=butjtu+bvtktvbibbia

我 们 将 给 定 的 系 统 a 中 的 所 有 元 素 从 小 到 大 排 序 , 对 排 序 过 后 的 每 一 个 a i , 仅 需 判 断 a i 能 否 由 之 前 的 元 素 凑 出 即 可 。 将 能 被 凑 出 的 元 素 删 除 , 剩 下 的 元 素 构 成 的 系 统 就 是 系 统 b 。 我们将给定的系统a中的所有元素从小到大排序,对排序过后的每一个a_i,仅需判断a_i能否由之前的元素凑出即可。\\将能被凑出的元素删除,剩下的元素构成的系统就是系统b。 aaiaib

这 样 , 对 每 个 a i , 考 虑 前 i − 1 个 元 素 , 能 否 选 择 几 个 元 素 使 得 其 和 恰 好 为 a i , 若 不 能 则 保 留 a i , 答 案 + + 。 问 题 就 转 化 为 一 个 求 完 全 背 包 决 策 数 量 问 题 。 这样,对每个a_i,考虑前i-1个元素,能否选择几个元素使得其和恰好为a_i,若不能则保留a_i,答案++。\\问题就转化为一个求完全背包决策数量问题。 aii1使aiai++

代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=25010;   //凑出的所有数的范围就是ai的范围

int T,n,a[N],f[N];

int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        sort(a+1,a+n+1);
        
        memset(f,0,sizeof f);
        f[0]=1;
        int m=a[n];
        
        int cnt=0;
        for(int i=1;i<=n;i++)
        {
            if(f[a[i]]==0) cnt++;
            for(int j=a[i];j<=m;j++)
                f[j]+=f[j-a[i]];
        }
        
        cout<<cnt<<endl;
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值