【OJ4976】硬幣,神奇的揹包
硬幣
總時間限制: 1000ms 記憶體限制: 262144kB
描述
宇航員Bob有一天來到火星上,他有收集硬幣的習慣。於是他將火星上所有面值的硬幣都收集起來了,一共有n種,每種只有一個:面值分別為a1,a2… an。 Bob在機場看到了一個特別喜歡的禮物,想買來送給朋友Alice,這個禮物的價格是X元。Bob很想知道為了買這個禮物他的哪些硬幣是必須被使用的,即Bob必須放棄收集好的哪些硬幣種類。飛機場不提供找零,只接受恰好X元。
輸入
第一行包含兩個正整數n和x。(1 <= n <= 200, 1 <= x <= 10000)
第二行從小到大為n個正整數a1, a2, a3 … an (1 <= ai <= x)
輸出
第一行是一個整數,即有多少種硬幣是必須被使用的。
第二行是這些必須使用的硬幣的面值(從小到大排列)。
樣例輸入
5 18
1 2 3 5 10
樣例輸出
2
5 10
提示
輸入資料將保證給定面值的硬幣中至少有一種組合能恰好能夠支付X元。
如果不存在必須被使用的硬幣,則第一行輸出0,第二行輸出空行。
寫在前面:啥也不說了,今天就寫the flash的列傳了
——————————————————————————————————————————————
思路:我首先想到用01揹包求方案個數,接下來就有點虛了,最後對每種硬幣進行列舉,去掉每種硬幣再進行01揹包dp,如果最後方案數為0,那麼這種硬幣一定是必須用的,但是這種方法時間複雜度為O(n×n×x),接近4*10^8,即使加了一些優化(當方案數>0即退出當前迴圈)也還是TLE,無奈請教聰哥(the flash),聰哥的方法是再開一個g陣列,得出對於去掉第i種硬幣,體積為j時g[j]=f[j]-g[j-a[i]],即g代表去掉某一種硬幣後的方案數(實在覺得自己說不清楚,還是看程式碼吧),這樣就可以把時間複雜度縮小到O(n×x)
程式碼:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,a[21000],f[20010],x;
int g[20010],num[20010];
int in()
{
int t=0;
char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
return t;
}
main()
{
n=in();x=in();
for (int i=1;i<=n;i++) a[i]=in();
f[0]=1;
for (int i=1;i<=n;i++)
for (int j=x;j>=a[i];j--)
f[j]+=f[j-a[i]];
//得出不去除硬幣時的方案數
for (int i=1;i<=n;i++)
{
memset(g,0,sizeof(g));
for (int j=0;j<=x;j++)
if (j-a[i]>=0 )
g[j]=f[j]-g[j-a[i]];
else g[j]=f[j];
if (g[x]==0) num[++num[0]]=a[i];
}
printf("%d\n",num[0]);
for (int i=1;i<=num[0];i++) printf("%d ",num[i]);
}