今天我们来聊聊一个有意思的问题:给定n枚硬币,指定一个数额s,问总和为s的硬币数最少是多少枚?为了突出问题本质,假定一定存在硬币组合,使得总和为s。看似很简单对不对,不过还是有很多东西可以聊聊,下面介绍4种解法。
就像别人来找零,要你给他115块钱,那么你肯定不会给他115张一块钱的币,在币种充足情况下,你会这么核算:100+10+5。事实上,贪心算法就是这么做的。
#include
#include
using namespace std;
int main() {
int C,n;
int coin[100];
cin >> C >> n;
for (int i = 1; i <= n; i++) {
cin >> coin[i];
}
sort(coin+1,coin+n+1);
int sum = 0;
int i = n;
int ans = 0;
while (sum != C) {
if (sum + coin[i] <= C) {
ans ++;
sum += coin[i];
}
i--;
}
cout << ans << endl;
return 0;
}
给你25 10 10 10 3 1 1这七枚硬币凑足30元(不多不少,就要30),那么按照贪心,选择应该是25+3+1+1,这四枚硬币。但实际上,10+10+10这三枚就够了。应当说明的是,贪心算法在常见币种面额情况下还是能够得到最优答案的,但如若从整体上来说,只能说贪心法得到的是近似最优解。
对于每一枚硬币,无非就是选与不选。于是,暴力回溯的方法出来了:
#include
#include
using namespace std;
int C,n;
int v[10];
int ans = 0x7fffffff;
void dfs(int i, int sum, int cnt) {
if (i > n) {
if (sum == C) {
ans = min(ans,cnt);
}
return ;
}
if(sum == C) {
ans = min(ans,cnt);
return ;
}
dfs(i+1,sum,cnt);
dfs(i+1,sum+v[i],cnt+1);
}
int main() {
cin >> C >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
dfs(1,0,0);
cout << ans << endl;
return 0;
}
选与不选之中,选记为1,不选记为0,那么这些硬币的选择状态会形成一个位向量。例如,对于上述贪心法,位向量是:1000111。这样,我们就可以枚举0000000-1111111的所有位向量,看看哪个是凑足钱数而币数又最少的方案。借助bitset:
#include
#include
#include
using namespace std;
int main() {
int C,n;
cin >> C >> n;
int coin[12];
int ans = 1 << 21;
for (int i = 0; i < n; i++) {
cin >> coin[i];
}
for (int i = 0; i < (1 << n); ++i) {
bitset<10> bs(i);
int sum = 0;
for (int j = 0; j < 10; ++j) {
sum = sum + bs[j]*coin[j];
}
if (sum == C) {
ans = min(ans,(int)bs.count()); // count函数用来求bitset中1的位数
}
}
cout << ans << endl;
return 0;
}
(这里问题性质有所改变,币面值给定,不限币数,凑足是≥的意思)
这种方法是准确而耗时又比较少的。
此处可以思考一分钟——该问题具有最优子结构。
定义dp[i][j]的意义为:在前i个物品恰好凑足j价值的最少币数。
#include
#include
#include
#include
using namespace std;
int dp[12][50];
int main() {
int C,n;
cin >> C >> n;
int coin[12];
for (int i = 1; i <= n; i++) {
cin >> coin[i];
}
for (int i = 1; i <= C; i++) {
dp[1][i] = ceil((double)i/coin[1]);
}
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= C; j++) {
if (j <= coin[i]) {
dp[i][j] = 1;
}
else {
dp[i][j] = min(dp[i-1][j],dp[i-1][j-coin[i] ]+1);
}
}
}
cout << dp[n][C] << endl;
return 0;
}
如有错误,欢迎指正。