[Bzoj3233][Ahoi2013]找硬幣[基礎DP]
3233: [Ahoi2013]找硬幣
Time Limit: 10 Sec Memory Limit: 64 MB
Submit: 924 Solved: 482
[Submit][Status][Discuss]
Description
小蛇是金融部部長。最近她決定制造一系列新的貨幣。假設她要制造的貨幣的面值為x1,x2,x3… 那麽x1必須為1,xb必須為xa的正整數倍(b>a)。例如 1,5,125,250就是一組合法的硬幣序列,而1,5,100,125就不是。不知從哪一天開始,可愛的蛇愛上了一種萌物——兔紙!從此,小蛇便走上了遇上兔紙娃娃就買的不歸路。某天,小蛇看到了N只可愛的兔紙,假設這N 只兔紙的價錢分別是a1,a2…aN。現在小蛇想知道,在哪一組合法的硬幣序列下,買這N只兔紙所需要的硬幣數最少。買兔紙時不能找零。
Input
第一行,一個整數N,表示兔紙的個數 第二行,N個用空格隔開的整數,分別為N只兔紙的價錢
Output
一行,一個整數,表示最少付的錢幣數。
Sample Input
2 25 102
Sample Output
4
HINT
樣例解釋:共有兩只兔紙,價錢分別為25和102。現在小蛇構造1,25,100這樣一組硬幣序列,那麽付第一只兔紙只需要一個面值為25的硬幣,第二只兔紙需要一個面值為100的硬幣和兩個面值為1的硬幣,總共兩只兔紙需要付4個硬幣。這也是所有方案中最少所需要付的硬幣數。
1<=N<=50, 1<=ai<=100,000
分析:
本來湊面值的題就是十分親民的題,再加上數據又十分親民,所以就是一道十分親民的題……。
定義狀態f[i]表示最大硬幣為i面值時的最少硬幣。轉移方程f[i] = min(f[i],f[j] - sum((a[k] / i)* (i / j - 1)));(j為i的因子,k為每只兔子價格)
為什麽這麽轉移,因為比如說我們有三枚三元硬幣,我們可以轉換成一枚九元硬幣。
很簡單,比如說我們有一只兔子價格為28,用1元硬幣去湊會用28枚(即f[1] = 28),用1、3去湊會有 f[3] = f[1] - 28 / 3 * (3 / 1 - 1) = 10枚硬幣,用1,3,9去湊會有f[9] = f[3] - 28 / 9 * (9 / 3 - 1) = 4枚硬幣。
其實轉移是很簡單的,但是發現i的範圍是100,000 ,j是i的因子需要去轉化,k是每只兔子,復雜度很高的。我們可以考慮省掉j。即我們用遞推。
可以由f[j]倒推f[i]。
發現100000 內每個數 不停加本身,推到100000,再乘上50只兔子的復雜度才幾千萬,隨便卡過。
我一開始是這麽做的,於是4.6S過了(數據太親民了)。
其實後面我想了一下還可以優化,因為比如說f[27]由f[9]推過去肯定比由f[3]推過去優,所以對於每個數我們只用去推它的質數倍數就好了。
設M =100,000,N = 50;
K 為 M內所有數乘以質數倍推到100000的復雜度(好像這個數才十萬左右....)
篩素數(M loglog M),遞推(N * K)所以復雜度是(M log log M + N * K)。然後就從4.6S降到了1.6S。話說數據是真親民啊……
附上AC代碼:
# include <iostream> # include <cstdio> # include <cstring> # include <algorithm> using namespace std; const int N = 102; const int M = 1e5 + 12; int a[N],n; int f[M],ans,sum,maxn,cnt,p[M]; bool vis[M]; void shai(){ for(int i = 2;i < M;i++){ if(!vis[i])p[++cnt] = i; for(int j = 1;j <= cnt;j++){ if(p[j] * i >= M)break; vis[p[j] * i] = true; if(i % p[j] == 0)break; } } } void Init(){ memset(f,0x3f3f3f3f,sizeof f); } int dp(int i,int j){ int ans = f[j]; for(int k = 1;k <= n;k++){ ans -= (a[k] / i) * (i / j - 1); } return ans; } int main(){ Init(); scanf("%d",&n); for(int i = 1;i <= n;i++){ scanf("%d",&a[i]);sum += a[i]; maxn = max(maxn,a[i]); } f[1] = sum;ans = f[1]; for(int i = 2;i <= maxn;i++){ f[i] = min(f[i],dp(i,1)); ans = min(ans,f[i]); } shai(); for(int i = 2;i <= maxn;i++){ for(int j = 1;j <= cnt;j++){ if(p[j] * i > maxn)break; f[p[j] * i] = min(f[p[j] * i],dp(p[j] * i,i)); ans = min(ans,f[p[j] * i]); } } printf("%d\n",ans); }
[Bzoj3233][Ahoi2013]找硬幣[基礎DP]