本题是一个完全背包问题,需要对三重循环进行优化。
容易想到用(i, j)
表示从前i
个硬币中选取总价值为j
的方案中,使用硬币数目的集合。f(i, j)
表示其最小值。
则有 f(i, j) = min(f(i - 1, j - k*w[i]) + k), 其中 k = {0, 1, ..., j / w[i]},w[i]是第i个硬币的价值
。
于是本题在对i
,j
循环之外还要额外对k
循环,一定会超时。需要进行优化。
我们发现,由
f(i, j) = min(f(i - 1, j - k*w[i]) + k), 其中 k = {0, 1, ..., j / w[i]}
(1)
可得
f(i, j - w[i]) = min(f(i - 1, j - (k + 1)*w[i]) + k), 其中 k = {0, 1, ..., j / w[i]}
(2)
把(2)代入(1),可得
f(i, j) = min(f(i - 1, j), f(i, j - w[i]) + 1)
优化完成
下面给出代码,使用滚动数组优化(不用滚动数组也能过)。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int M = 25, N = 50010;
int f[N];
int w[M];
int main(){
int n, m;
cin>>n>>m;
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int i = 1; i <= m; i++) cin>>w[i];
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
if(j >= w[i]) f[j] = min(f[j], f[j - w[i]] + 1);
cout<<f[n];
return 0;
}