题目类型 DP
题目意思
给出 n (1 <= n <= 100) 种硬币和一个数 m (m <= 10000) 其中每种硬币给出面值(1-120) 和 数量(1-10000)
现在应付的钱数是 m, 使用给出的硬币付钱后可能需要找钱 找钱的时候硬币的数量是无穷大的
问付钱时用的硬币数 + 找钱所用的硬币数 最小是多少
解题方法
多重背包 + 完全背包
付钱时用多重背包 找钱时用完全背包即可
这道题重要的地方是确定边界的大小
假设硬币中面额最大的硬币的面额是 n_max 可以证明付钱的边界最大是 n_max * n_max + m
证明如下 :
用反证法 首先假设某个最优方案付钱的数目已经大于 n_max*n_max + m 说明需要找钱的数目大于 n_max * n_max
这时找钱的硬币数量 > n_max(因为最大的面额才是n_max) 假设找钱所用的x枚硬币是 N1, N2, ... Nx (x > n_max)
先不管找钱所用的硬币的面额 假设 S(i) = N1+N2+...+Ni 设 Mod(i) = S(i) % n_max
那么 Mod(1) -> Mod(x) 有 x 个数 (x > n_max) 而 Mod(i) 的取值范围是 (0->n_max-1 共n_max个数)
根据鸽巢原理其中至少有两个Mod()的值是相等的(假设是 Mod(i)和 Mod(j) 其中 j>i ) Mod()值相等意味着 ( S(j)-S(i) ) % n_max == 0 即第 i+1枚硬币到第 j枚硬币的面额加起来是 n_max的倍数 这时如果这几枚硬币的面额不全是 n_max 的话就意味着可以用数量更少的面额为n_max的硬币来代替原来的硬币
同理 付钱的硬币数量 > n_max 也可以找到几枚硬币的面额和是n_max的倍数 如果这几枚硬币不全是n_max的话所用的硬币总数同样可以更少
但是找钱和付钱的那几枚面额加起来的和是n_max的倍数的硬币都是n_max(或者都含有面额为n_max的硬币)的话 虽然都不能替换成数量更少的硬币 但是这种情况下显然有几枚面额为n_max的硬币是不需要用到的 (又给又还其实相当于不需要给) 这样所需的硬币总数还是有更少的情况
因此假设不成立 付钱数应该是 <= n_max*n_max+m的
这个证明不太严谨
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 1<<29;
int dp[30000];
int dp2[30000];
int w[110], c[110];
int main() {
freopen("in", "r", stdin);
int n, V;
while(scanf("%d%d", &n, &V) != EOF) {
int n_max = -1;
for( int i=0; i<n; i++ ) scanf("%d", &w[i]), n_max = max(n_max, w[i]);
for( int i=0; i<n; i++ ) scanf("%d", &c[i]);
int V2 = n_max * n_max + V; //最大的付钱数
for( int i=0; i<=V2; i++ ) dp[i] = INF; dp[0] = 0;
for( int i=0; i<=V2; i++ ) dp2[i] = INF; dp2[0] = 0;
for( int i=0; i<n; i++ ) {
int k = 1;
while(k <= c[i]) {
for( int j=V2; j>=w[i]*k; j-- ) dp[j] = min(dp[j], dp[j-w[i]*k]+k);
c[i] -= k;
k *= 2;
}
if(c[i]) {
for( int j=V2; j>=w[i]*c[i]; j-- ) dp[j] = min(dp[j], dp[j-w[i]*c[i]]+c[i]);
}
}
for( int i=0; i<n; i++ ) {
for( int j=w[i]; j<=V2-V; j++ ) { // 最大的找钱数
dp2[j] = min(dp2[j], dp2[j-w[i]]+1);
}
}
int ans = INF;
for( int i=0; i<=V2-V; i++ ) ans = min(ans, dp[V+i]+dp2[i]); // 枚举找钱数
if(ans == INF) printf("-1\n");
else printf("%d\n", ans);
}
return 0;
}