【数学】【背包】【NOIP2018】P5020 货币系统

传送门

Description

在网友的国度中共有 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

Input

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

接下来按照如下格式分别给出 T 组数据。 每组数据的第一行包含一个正整数 n。接下来一行包含 n 个由空格隔开的正整数 a[i]

Output

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

Solution

我居然被这个题差点搞退役……

观察题目给的样例,发现选择的货币集合是原集合的一个子集。

考虑证明,使用反证法,假设被选择的集合中,k是最小的在原集合中没有出现的数。因为新集合的元素能表示的原集合一定能表示,所以原集合一定存在一组数使得他们能表示出k。那么将那组数作为k,在后面可以表示k能表示出的所有数字,同时会比选择k少选择一个数,选择更优。证毕。

于是这道题选择的一定是原集合的一个子集。

考虑按照证明的思路,本题可以进一步转化为选择最少的数字拼出集合中的所有数字。这个操作可以通过完全背包实现:按照集合元素大小将元素从小到大排序,使用bool背包求出所有能被表示的数。在扫到第i个数字时,如果第i个数字不能被表示出,则将其选进集合中,使用它更新答案,否则跳过。

时间复杂度O(Tnmax{a})

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define rg register
#define ci const int
#define cl const long long

typedef long long int ll;

template <typename T>
inline void qr(T &x) {
	rg char ch=getchar(),lst=' ';
	while((ch > '9') || (ch < '0')) lst=ch,ch=getchar();
	while((ch >= '0') && (ch <= '9')) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(lst == '-') x=-x;
}

namespace IO {
	char buf[120];
}

template <typename T>
inline void qw(T x,const char aft,const bool pt) {
	if(x < 0) {x=-x,putchar('-');}
	rg int top=0;
	do {IO::buf[++top]=x%10+'0';} while(x/=10);
	while(top) putchar(IO::buf[top--]);
	if(pt) putchar(aft);
}

const int maxn = 120;
const int maxt = 25010;

int t;
int n;
int MU[maxn];
bool frog[maxt];

void clear();

int main() {
	qr(t);
	while(t--) {
		clear();
		qr(n);
		for(rg int i=1;i<=n;++i) qr(MU[i]);
		int ans=0;
		std::sort(MU+1,MU+1+n);
		frog[0]=true;
		for(rg int i=1;i<=n;++i) if(!frog[MU[i]]) {
			++ans;
			for(rg int j=0;j<=MU[n];++j) if(frog[j]) {
				int k=j+MU[i];
				if(k <= MU[n]) frog[k]=true;
				else break;
			}
		}
		qw(ans,'\n',true);
	}
	return 0;
}

void clear() {
	n=0;
	memset(MU,0,sizeof MU);
	memset(frog,0,sizeof frog);
}

Summay

惨象,已使我目不忍视了。爆零,尤使我耳不忍闻。我还有什么话可说呢?沉默呵,沉默呵,不在沉默中爆发,就在沉默中灭亡。

posted @   一扶苏一  阅读(214)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示