[bzoj3233] [Ahoi2013]找硬币

  一开始没什么思路...后来想到确定最大硬币面值就知道其他面值能取多少了。。而且结果是可以由较小的面值转移过来的。

  f[i]表示最大面值为i时的最小硬币数。a[i]表示第i个物品的价钱。

  f[i]=min{   f[ i / j ]- sum{  a[ k ] / i * ( j-1 )  }   },(j是i的因数)

    对于物品k,我们能用a[k]/i 枚面值为i的硬币。显然肯定都用上啦。

    而且现在我们用面值i 的硬币拼出来的价钱,本来肯定都是用面值为( i / j )的硬币拼的。(因为j是i的因数,所以能用( i / j )的拼;又因为( i / j )是之前的最大面额,所以肯定会用)

    所以我们用a[k]/i枚面值为i 的硬币能够减少a[k]/i*(j-1)枚面值为( i / j )的硬币的使用。(感谢JSZX11556老司机指正。。已修改)

 

  然后又因为显然,在符合条件的情况下面值种类越多越好(至少不会更差)。。所以我们使j为质数就好了。(若j是合数的话,我们可以先增加其他面额,然后从另一种面值转移到i,所以j不取合数对答案没影响)

  线性筛质数的时候可以顺便求出每个数的最小质因数,然后就可以做到只枚举i的质因数了。

  时间复杂度O(n*sum*loglogsum),(sum为所有兔纸价钱的总和)(复杂度分析参考埃氏筛法= =)

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #define ll long long
 5 using namespace std;
 6 int mn[100023],pri[100233],f[100023],cnt,mx;
 7 int a[55];
 8 bool cant[100233];
 9 int i,j,k,n,m,ans;
10 
11 inline void prerun(int mx){
12     for(i=2;i<=mx;i++){
13         if(!cant[i])pri[++cnt]=i,mn[i]=i;
14         for(k=i<<1,j=1;j<=cnt&&k<=mx;k=i*pri[++j]){
15             cant[k]=1,mn[k]=pri[j];
16             if(!(i%pri[j]))break;
17         }
18     }
19 }
20 
21 int ra;char rx;
22 inline int read(){
23     rx=getchar(),ra=0;
24     while(rx<'0'||rx>'9')rx=getchar();
25     while(rx>='0'&&rx<='9')ra*=10,ra+=rx-48,rx=getchar();return ra;
26 }
27 inline int min(int a,int b){return a<b?a:b;}
28 
29 int main(){
30 //    memset(f,60,(n+1)<<2);f[1]=0;
31     n=read();
32     for(i=1;i<=n;i++)mx=max(mx,a[i]=read()),f[1]+=a[i];
33     prerun(mx);
34     register int i,j,k,tmp,sm;
35     for(i=2;i<=mx;i++)f[i]=f[1];ans=f[1];
36     for(i=2;i<=mx;ans=min(ans,f[i]),i++)
37         for(tmp=i;tmp>1;f[i]=min(f[i],sm)){
38     //        printf("  %d    %d\n",f[i],f[i/mn[tmp]]);
39             for(sm=f[j=i/mn[tmp]],k=1;k<=n;k++)sm-=a[k]/i*(mn[tmp]-1);
40             while(mn[tmp]==mn[tmp/mn[tmp]])tmp/=mn[tmp];tmp/=mn[tmp];
41         }
42     printf("%d\n",ans);
43     return 0;
44 }
View Code

 

posted @ 2016-02-15 17:43  czllgzmzl  阅读(537)  评论(0编辑  收藏  举报