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种货币<=>n种物品组成面值为m的货币<=>装满容量为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\}。 简单描述题意:给定一个货币系统a,包含n种货币面额{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}与货币系统a等价,当且仅当系统a能够凑成的所有面额均能由系统b中的货币凑出。
抽 象 地 , 对 表 达 式 ∑ 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=1naiti所能得到的任意值,均有∑j=1mbjtj与之相等,其中ti与tj均是大于等于0的正整数。
求 集 合 b 中 元 素 的 最 小 个 数 。 求集合b中元素的最小个数。 求集合b中元素的最小个数。
题
解
:
若
系
统
b
与
系
统
a
等
价
,
且
系
统
b
是
最
优
方
案
,
则
系
统
b
必
然
满
足
下
列
三
条
性
质
:
题解:\\若系统b与系统a等价,且系统b是最优方案,则系统b必然满足下列三条性质:
题解:若系统b与系统a等价,且系统b是最优方案,则系统b必然满足下列三条性质:
①
、
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,...,an一定可以由系统b表示出来。②、b1,b2,...,bm一定能由系统a中的某一个数单独表示出来。③、b1,b2,...,bm中的任一数值一定不能被其他的bi表示出来。
说 明 性 质 ② : 假 设 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),又因为aj可由某个bu表示,ak可由某个bv表示,假设aj=butu,ak=bvtv,(tu,tv>=1),则bi=butjtu+bvtktv,说明bi是多余的,可以删除。因此,最优解的系统b中的任何一个元素bi,均能由系统a中的某个元素单独表示。
我 们 将 给 定 的 系 统 a 中 的 所 有 元 素 从 小 到 大 排 序 , 对 排 序 过 后 的 每 一 个 a i , 仅 需 判 断 a i 能 否 由 之 前 的 元 素 凑 出 即 可 。 将 能 被 凑 出 的 元 素 删 除 , 剩 下 的 元 素 构 成 的 系 统 就 是 系 统 b 。 我们将给定的系统a中的所有元素从小到大排序,对排序过后的每一个a_i,仅需判断a_i能否由之前的元素凑出即可。\\将能被凑出的元素删除,剩下的元素构成的系统就是系统b。 我们将给定的系统a中的所有元素从小到大排序,对排序过后的每一个ai,仅需判断ai能否由之前的元素凑出即可。将能被凑出的元素删除,剩下的元素构成的系统就是系统b。
这 样 , 对 每 个 a i , 考 虑 前 i − 1 个 元 素 , 能 否 选 择 几 个 元 素 使 得 其 和 恰 好 为 a i , 若 不 能 则 保 留 a i , 答 案 + + 。 问 题 就 转 化 为 一 个 求 完 全 背 包 决 策 数 量 问 题 。 这样,对每个a_i,考虑前i-1个元素,能否选择几个元素使得其和恰好为a_i,若不能则保留a_i,答案++。\\问题就转化为一个求完全背包决策数量问题。 这样,对每个ai,考虑前i−1个元素,能否选择几个元素使得其和恰好为ai,若不能则保留ai,答案++。问题就转化为一个求完全背包决策数量问题。
代码:
#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;
}