[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 }