[完全揹包] 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]\)
兩個貨幣系統\((n,a)\)和\((m,b)\)是等價的,當且僅當對於任意非負整數\(x\),它要麼均可以被兩個貨幣系統表出,要麼不能被其中任何一個表出。
現在網友們打算簡化一下貨幣系統。他們希望找到一個貨幣系統\((m,b)\),滿足\((m,b)\)與原來的貨幣系統\((n,a)\)等價,且\(m\)儘可能的小。他們希望你來協助完成這個艱鉅的任務:找到最小的\(m\)。
輸入輸出格式
輸入格式:
輸入檔案的第一行包含一個整數\(T\)表示資料的組數。
接下來按照如下格式分別給出\(T\)組資料。 每組資料的第一行包含一個正整數\(n\)
輸出格式:
輸出檔案共有\(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);
}
}