[P5228 [AHOI2013] 找硬币]题解-DP

发布时间 2023-07-18 16:58:47作者: H_W_Y

20230718
传送门
发现\(a_i\)\(n\)都很小
也就是说我的面值最大是\(10^5\)
这样最大面值就可以用来做下标

其实最开始也不知道怎么做
我们现在考虑dp
\(dp[i]\)表示最大面值为\(i\)时的最小钱币数
由于\(i* j\)一定是\(i\)的倍数
所以\(dp[i * j]\)是可以从\(dp[i]\)转移过来
转移过程中
我可以把每一个价钱里所用的\(j\)个价值为\(i\)的钱币合并成为一个
类似倍增的一步一步逼近的思想
每一个\(a_i\)中最多可以把\(a_i /(i* j)\)组钱币优化
这样统计总共有\(cnt\)组优化
那么\(dp[i * j]=min(dp[i* j],dp[i]-cnt*(j-1))\)

时间复杂度为\(O(n* maxn)\)
关键就在于想到用dp来维护

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5;
int n,a[55],dp[maxn+5],ans=0,cnt=0;

int main(){
  /*2023.7.18 H_W_Y P5228 [AHOI2013] 找硬币 dp*/
  memset(dp,0x3f,sizeof(dp));dp[1]=0;
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&a[i]),dp[1]+=a[i];
  ans=dp[1];
  for(int i=1;i<=maxn/2;i++)
    for(int j=2;j*i<=maxn;j++){cnt=0;
      for(int k=1;k<=n;k++) cnt+=a[k]/(i*j);
      dp[i*j]=min(dp[i*j],dp[i]-cnt*(j-1));
      ans=min(ans,dp[i*j]);
	}
  printf("%d\n",ans);
  return 0;
}