[完全背包] NOIP2018 货币系统
[完全背包] NOIP2018 货币系统
题面
题目描述
在网友的国度中共有\(n\)种不同面额的货币,第\(i\)种货币的面额为\(a[i]\),你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为\(n\)、面额数组为\(a[1..n]\)的货币系统记作\((n,a)\)。
在一个完善的货币系统中,每一个非负整数的金额\(x\)都应该可以被表示出,即对每一个非负整数\(x\),都存在\(n\)个非负整数\(t[i]\)满足\(a[i] \times 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\)。
Sample Input
2
4
3 19 10 6
5
11 29 13 19 17
Sample Output
2
5
题解
我们先证明一个结论:
那么要使\(m\)最小,\((m,b)\)中的每一个元素必定存在于\((n,a)\)中
下面给出这个结论的证明:
假设\((m,b)\)中包含一个不存在于\((n,a)\)中的元素
则有两种情况:
- 这个元素可以被\((n,a)\)中的元素表示,那么此时\(m\)不满足最小,因为这个元素可以被剔除
- 这个元素不能被\((n,a)\)中的元素表示,那么此时不满足\((n,a)\)与\((m,b)\)等效
所以\((m,b)\)中不包含任何不存在于\((n,a)\)中的元素
那么我们就可以直接从\((n,a)\)中剔除可以被自己表示的元素就可以得到\((m,b)\)了
只需要背包一下就行了,思路还是很清晰的
上代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=30001;
int n,ans,t,maxn=0,a[MAXN],vis[MAXN];
bool tf[MAXN],dp[MAXN];//tf[i]表示数字i是否出现在(n,a)中
//dp[i]表示数字i是否可以被(n,a)表示
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);ans=n;
memset(vis,0,sizeof(vis));
memset(tf,0,sizeof(tf));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){scanf("%d",&a[i]);tf[a[i]]=true;maxn=max(maxn,a[i]);}
sort(a+1,a+1+n);
dp[0]=true;
for(int i=a[1];i<=maxn;i++){
for(int j=1;j<=vis[0];j++)if(dp[i-vis[j]])dp[i]=true;//背包
if(dp[i]){
if(tf[i])ans--;//如果可以被表示,则剔除
}else{
if(tf[i]){vis[++vis[0]]=i;dp[i]=1;}//否则用它来表示其他的数值
}
}
printf("%d\n",ans);
}
}