[BZOJ 3233] 找硬币
Link:
Solution:
在本蒟蒻看来算是一道比较神的$dp$了
一开始转移方程都没看出来……
首先,如果确定了最大面值,是能推出其他面值的所有可能值的
从而发现最大面值能由较小的面值转移过来:
$dp[i]=min\{ dp[i/j]-sum\{ a[k]/i*(j-1)\} \}$
(其中$a[k]/i$是大面值需要的个数,$a[k]/(i/j)$是小面值需要的个数,余数不处理)
下面就要处理$j$的取值了,
首先要看到一个比较明显的性质:用$dp[i]$转移肯定不会比用$dp[i/j]$差,毕竟种类更多
于是$j$应为质数($i$的质因数),否则$i/j<i/MinPrime$,从而$dp[i/MinPrime]$一定不比$dp[i/j]$差
因此不取合数对答案没有影响
在线性筛时可以顺便求出$x$的最小质因数$mn[x]$,方便后面求出$x$的每一个质因数
Code:
#include <bits/stdc++.h> using namespace std; const int MAXN=1e5+10; int n,tot,mx,cnt,t,mn[MAXN],dat[MAXN],pri[MAXN],vis[MAXN],dp[MAXN],res; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&dat[i]),mx=max(mx,dat[i]),tot+=dat[i]; mn[1]=1; for(int i=2;i<=mx;i++) //线性筛 { if(!vis[i]) pri[++cnt]=i,mn[i]=i; for(int j=1;j<=cnt && i*pri[j]<=mx;j++) { vis[i*pri[j]]=1;mn[i*pri[j]]=pri[j]; if(i%pri[j]==0) break; } } for(int i=1;i<=mx;i++) dp[i]=tot;res=tot; for(int i=2;i<=mx;res=min(res,dp[i]),i++) for(int j=i;j>1;dp[i]=min(dp[i],t)) { t=dp[i/mn[j]]; for(int k=1;k<=n;k++) t-=dat[k]/i*(mn[j]-1); while(mn[j]==mn[j/mn[j]]) j/=mn[j]; j/=mn[j]; } printf("%d",res); return 0; }
Review:
1、关于质因数分解的优化
只能算是常数上的优化吧
由于筛法时我们要保证合数只被自己的最小质因数筛去,
于是我们顺便记录下每个数最小的质因数$mn[x]$,再一步步推出下一个质因数
推导的方式
while(mn[j]==mn[j/mn[j]]) j/=mn[j]; j/=mn[j];
2、$dp$的思考技巧
如果不清楚对什么量$dp$,可以查找那些量可以推出上一步/下一步的值,往往对这些量$dp$