1. 问题描述:
n种硬币,面值分别是:v[1],v[2],……v[n]。给定非负整数s,可以选用多少个硬币,使得面值之和恰好为s?
求出硬币数目的最大值和最小值。
接下来考虑硬币问题最长路和最短路的问题相似只考虑最长路
例如int dp(int s)//求最大的硬币数量
{
int &ans = d[s];
if(ans>=0)
return ans;
for(int i = 1; i <= n; ++i)
{
if(s >= v[i])
{
ans = max(ans, dpmax(s - v[i]) + 1);
}
}
return ans;
}
针对上面计算最长路的问题 由于路径长度可以为0,S本身是0因此d=0和d的全段为0都不可以,会影响结果
由于节点S不一定会走到0所以需要特殊值表示如果S走不到但是返回值却是0则可能误认为走到0所以如下修改,
int dp(int s)//求最大的硬币数量
{
int &ans = d[s];
if(ans!=0)
return ans;
ans=-(1<<30);
for(int i = 1; i <= n; ++i)
{
if(s >= v[i])
{
ans = max(ans, dpmax(s - v[i]) + 1);
}
}
return ans;
}
最好的方法是加一个数组vis来表示状态i是否被访问过。
#include<iostream>
#include<cstring>
using namespace std;
int n, s, v[100],d[100], vis[100];//vis[i]=0:标志状态i没有计算过;否则计算过
int dpmax(int s);
int dpmin(int s);
void print(int*d,int s);//打印出组成S的硬币大小。
int main()
{
cin >> n >> s;
for(int i = 1; i <= n; ++i)
{
cin >> v[i];
}
memset(vis, 0, sizeof(vis));
vis[0]=1; //记忆化搜索的初始化必须将除了目标状态的所有状态设置为0
d[0]=0;
cout << dpmax(s) << endl;
print(d,s);
cout<<endl;
memset(vis, 0, sizeof(vis));
vis[0]=1;
cout << dpmin(s) << endl;
print(d,s);
return 0;
}
int dpmax(int s)//求最大的硬币数量
{
if(vis[s])//表示状态是否计算出
{
return d[s];
}
vis[s] = 1;//状态已算出标记1
int &ans = d[s];//d数组中储存的是从0到金额中每个金额所需的最大硬币数
ans = -(1<<30);
for(int i = 1; i <= n; ++i)//此函数利用的是一种递归思想并且找出最小值
{
if(s >= v[i])
{
ans = max(ans, dpmax(s - v[i]) + 1);//先回归到最小状态,然后一个个回归在上一个状态下找出他的
//最小值返回数组d然后在回归上个状态就这样递归如果正好可以找到一个已经找到的状态直接调用d数组中得值
//另外在给的硬币数值的数组v中要遍历玩,如果找到了某个状态但还是可以找函数还是会返回直到找到最小值
//注意只有找到最后S是0是才会回归数组d才可以表示硬币为这个数目的状态 下面同理
}
//本题中要求求解最大最小值两个数值用递推更加方便
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=1<<30;
int n,s;
int v[100],minv[100],maxv[100],mincoin[100],maxcoin[100];
//mincoin、maxcoin:存储对应路径上硬币的大小
void print(int *d,int s);
int main()
{
cin>>n>>s;
for(int i=0;i<n;i++)
{
cin>>v[i];
}
minv[0]=maxv[0]=0;//递推的最下层
for(int i=1;i<=s;i++)//将最大最小值先赋值方便后面计算
{
minv[i]=INF;
maxv[i]=-INF;
}
for(int i=1;i<=s;i++)//硬币金额从1到N逐个求解
{
for(int j=0;j<n;j++)//数组中存在的金额数值通过循环找出最小次数的金额
{
if(i>=v[j])//硬币金额大于给的金额数值则可以继续找最小数
{
if(maxv[i]<maxv[i-v[j]]+1)//maxv[i]代表的是最小数第二个循环完结后就可得出
//i-v[j]代表的是 如果金额是10 给的硬币得值是5 则10-5是一次要加1在测出maxv[5]得值然后比较大小
{
maxv[i]=maxv[i-v[j]]+1;
maxcoin[i]=v[j];
}
if(minv[i]>minv[i-v[j]]+1)
{
minv[i]=minv[i-v[j]]+1;
mincoin[i]=v[j];
}
}
}
}
cout<<maxv[s]<<endl;
print(maxcoin,s);
cout<<minv[s]<<endl;
print(mincoin,s);
return 0;
}
void print(int*d,int s)
{
while(s)
{
cout<<d[s]<<" ";
s=s-d[s];
}
cout<<endl;
}
有关输出所有情况不解释 } return ans;}int dpmin(int s)//求最少的硬币数量{ if(vis[s])//用来标记状态是否计算出 { return d[s]; } vis[s] = 1; int &ans = d[s]; ans = 1<<30; for(int i = 1; i <= n; ++i) { if(s >= v[i]) { ans = min(ans, dpmin(s - v[i]) +1); } } return ans;}void print(int *d,int
s){ for(int i=0; i<n; i++) { if(s>=v[i]&&d[s]==d[s-v[i]]+1) { cout<<v[i]<<""; print(d,s-v[i]); return; } }}