1. 程式人生 > >[Bzoj3233][Ahoi2013]找硬幣[基礎DP]

[Bzoj3233][Ahoi2013]找硬幣[基礎DP]

printf 為什麽 轉移 但是 sam input 定制 limit 不能

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]