[完全背包] NOIP2018 货币系统
题面
题目描述
在网友的国度中共有(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)。
输入输出格式
输入格式:
输入文件的第一行包含一个整数(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
",ans);
}
}