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

猜你喜欢

转载自www.cnblogs.com/newera/p/9239139.html