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


    传送门

    Description

    在网友的国度中共有 (n) 种不同面额的货币,第 (i) 种货币的面额为 (a[i]),你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 (n)、面额数组为 (a[1..n]) 的货币系统记作 ((n,a))

    在一个完善的货币系统中,每一个非负整数的金额 (x) 都应该可以被表示出,即对每一个非负整数 (x),都存在 (n) 个非负整数 (t[i]) 满足 (a[i] imes 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,'
    ',true);
    	}
    	return 0;
    }
    
    void clear() {
    	n=0;
    	memset(MU,0,sizeof MU);
    	memset(frog,0,sizeof frog);
    }
    

    Summay

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

  • 相关阅读:
    P2422 良好的感觉
    拉格朗日插值
    C# 中的委托和事件(详解)
    异步委托
    ManualResetEvent详解
    快速理解C#高级概念事件与委托的区别
    拉格朗日多项式
    oracle 插入一个从别处查询获得字段的值
    decode和nvl的用法
    C#将像素值转换为图片
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/9971238.html
Copyright © 2020-2023  润新知