[BZOJ 3233] 找硬币

Link:

BZOJ 3233 传送门

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$

 

转载于:https://www.cnblogs.com/newera/p/9239139.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值