题干:有张数不限的n种面额纸币,求纸币总张数的最小值num,使得这num张纸币可以组合出1至x的任何金额。
输入:正整数n和x,n个表示面额的正整数。
n<=20,x<=10000,面额<=10000
输出:最少需要携带的零钱张数。 若不存在满足条件的方案,则输出-1。
要点:
1.对某个目标值最优不一定全局最优,不能按照一般找零问题的方法使用贪心
2.如果面值有1,一定能组合出所有目标值;如果没有1,一定不能组合出1。所以判断是否存在满足条件方案只需要判断面值是否有1。
从一开始就可以判断出所有目标值均有解,帮助了下文中问题1的解决。
错误思路:分别枚举每个目标金额的所有组成方案,再对所有方案进行排列组合,求出最少张数。
vector<vector<vector<pair<int, int>>>>target;//所有target组成一个vector
vector<vector<pair<int, int>>>solutions;//一个target的所有solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair
问题1:如何枚举所有组成方案?
一般思路是,可以遍历第每面值取0至tmp_target/value
tmp_target-=value_num[i].first*value_num[i].second;
但可以发现tmp_target=0则该方案已完成,tmp_target>=1则必有解
此时只需要获取已求得的target[tmp_target-1]即可。
所以对每个target[tgt-1],有n种情况,每次取value_num[i].first<=target一张,得到tmp_target=tgt-value_num[i].first。
若tmp_target=0,记为一种方案;
若tmp_target!=0,将target[tmp_target-1]的所有solution对应的value_num[i].second++即为target[tgt-1]在该种情况下的组合方案。
问题2:如何枚举所有组合方案的组合?
这步时间复杂度估计为O(n!),不可能全部枚举。注意到难点1的思路,其实
后面target的组合方案都是在前面某个方案的基础上加1张即可,总数是单调的。所以,应该从1开始,每次选择最优方案,并更新value_num。
对每个target,若当前value_num可以组成target,继续;若不能,选择一张尽可能大的面值。
怎么判断是否可以组成target?只要判断当前纸币总价值tmp是否大于等于target。实际上tmp应该满足target<=tmp<2*target,但是可以证明tmp一定小于2*target。该条件下一定可以减去一个小于该值的数(这些纸币一定可以组成小于它的所有target),使得剩余纸币总价值即为该值。
怎么证明val一定小于2*target?若tmp0<target1,则选取一张面值小于target1的纸币,更新为tmp1<2*target1<2*target2
为什么要选尽可能大的面值?
1.对于小于最大面值的target,大面值可以与原来的小面值组成后面一或多个target;
2.对于大于最大面值的target,每个都可以看做某个小于最大面值的target加上最大面值max的倍数。应当选择最大的面值,这样对于每max个数,只需要多加一张纸币,递增速度会慢。
vector<vector<pair<int, int>>>solution;//所有target的当前最优solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair
代码实现
#include<bits/stdc++.h>
//#include<iostream>
using namespace std;
vector<vector<pair<int, int>>>solution;//所有target的最优solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair
int main()
{
int n, x,num=0;
cin >> n >> x;
for (int i = 0; i < n; i++)//输入面值
{
cin >> tmp_pair.first;
value_num.push_back(tmp_pair);
}
sort(value_num.begin(), value_num.end());//按从小到大排序面值
if(value_num[0].first!=1)
{
cout << "-1";
return 0;
}
solution.push_back(value_num);
for (int tgt = 1; tgt <= x; tgt++)//遍历每个目标值
{
int tmp = 0;
for (int i = 0; i < n; i++)
tmp += value_num[i].second * value_num[i].first;
if (tmp >= tgt)
{
solution.push_back(value_num);
continue;
}
if (value_num[n-1].first < tgt)
{
solution.push_back(solution[tgt - value_num[n-1].first]);
solution[tgt][n-1].second++;
if(value_num[n - 1].second< solution[tgt][n - 1].second)
value_num[n-1].second++;
}
else
{
for (int i = n - 1; i >= 0; i--)
if (value_num[i].first <= tgt)
{
solution.push_back(solution[tgt - value_num[i].first]);
solution[tgt][i].second++;
value_num[i].second++;
break;
}
}
}
for (int i = 0; i < n; i++)
num += value_num[i].second;
cout << num;
return 0;
}