题目如上,该题目是01背包的一个扩展题,题解如下:
1、用一个数组a[ ]存储所有硬币的面值
2、对a数组进行排序(因为排序不会影响01背包的结果,而且题目要求输出最小序列,排序后方便后面做题)
3、用一个数组f[N][N]表示动态规划
状态表示:f[i][j]
集合:表示只从第i个硬币及它之后的硬币中选,总钱数恰好为j的方案数
属性:count(数量)
状态计算:
不选第i个硬币:f[i+1][j]
选第i个硬币:f[i+1][j-a[i]](前提:j>=a[i])
总结: f[i][j]=f[i+1][j]+f[i+1][j-a[i]]
这里为什么要从后往前推呢?因为我们要输出最小序列,从小到大排序后做动态规划,我们从前往后枚举时,如果当前数可选(假设当前枚举到了第i个数,即j>=a[i]且f[i+1][j-a[i]]不为0,如果f[i+1][j-a[i]]为0,则表示从第i+1个硬币到不了第i个硬币),则必选(输出a[i]),否则必不选
下面的f[1][m]表示如果所有的硬币可以表示为恰好m元钱时,则进入循环,否则输出No Solution
int i=1,j=m;
if(f[1][m]){
while(i<=n){
//如果j>=a[i]且可以由后一个状态转移过来,则必选这个数
if(j>=a[i]&&f[i+1][j-a[i]]){
cout<<a[i];
j-=a[i];
if(j!=0)cout<<" ";
}
i++;
}
cout<<endl;
}else cout<<"No Solution"<<endl;
f数组用之前要初始化,由于i是倒序枚举的,所以在状态表示中我们要用到i+1,所以对于所有的i从1~n+1,f[i][0](从第i个及i之后的硬币中选,总数刚好为0元,就只有一种情况,就是一个硬币都不选)所以都要初始化为1
#include<iostream>
#include<algorithm>
using namespace std;
const int N=10010,M=110;
int a[N];
int f[N][M];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);//由于求解要输出最小序列,在一开始就排序会好处理一点
for(int i=1;i<=n+1;i++)f[i][0]=1;
for(int i=n;i>=1;i--){
for(int j=m;j>=0;j--){//由于f现在是二维的,j从m或从0开始枚举都可以
f[i][j]=f[i+1][j];
if(j>=a[i])f[i][j]+=f[i+1][j-a[i]];
}
}
int i=1,j=m;
if(f[1][m]){
while(i<=n){
//如果j>=a[i]且可以由后一个状态转移过来,则必选这个数(因为是从小到大枚举)
if(j>=a[i]&&f[i+1][j-a[i]]){
cout<<a[i];
j-=a[i];
if(j!=0)cout<<" ";
}
i++;
}
cout<<endl;
}else cout<<"No Solution"<<endl;
return 0;
}
这样题目就完成啦!如果大家有更好的想法的话,欢迎大家的指点!!!