硬币问题(字典最小序)-DAG动态规划问题

来源:互联网 发布:ppt录制旁白软件 编辑:程序博客网 时间:2024/06/10 10:51


题目:

     有n种硬币,面值分别为V1,V2,...Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值!

   

分析:我们把每种面值看作一个点!表示“还需要凑足的面值”,初始状态为S,目标状态为0。那么若当前状态在i,每使用一个硬币j,状态便转移到i-Vj

因为求最大值跟求最小值类似,这里只贴记忆化搜索求最小值代码


代码: 下面dp1跟dp2功能相同。

不同dp2好理解一些。通过设置数组vis,标识是否访问过。牺牲空间换取效率,易读性。

<span style="font-size:24px;">#include"stdio.h"#include"stdlib.h"#include"algorithm"#include"string.h"#include"math.h"using namespace std;const int INF=1e9; const int max_coin=105;const int max_S=10005;int coin[max_coin];int d[max_S]; int vis[max_S];int n;//d(i)  从节点i出发到节点0的最长路径 int dp1(int S){//if(S==0)  return 0;int &ans=d[S];if(ans!=-1)  return ans; //记忆化搜索ans=INF;for(int i=0;i<n;i++) {if(S>=coin[i])    ans=min(ans,dp1(S-coin[i])+1);}ans=ans==INF?0:ans;return ans;}int dp2(int S) //牺牲空间换效率,容易理解 {//vis全部初始化为0 标记是否遍历过    if(vis[S]) return d[S];   vis[S]=1;   int &ans=d[S];   ans=INF;   for(int i=0;i<n;i++)     if(S>=coin[i])    ans=min(ans,dp2(S-coin[i])+1);   ans=ans==INF?0:ans;return ans;}int main(){int S;scanf("%d%d",&n,&S);for(int i=0;i<n;i++)  scanf("%d",&coin[i]);memset(d,-1,sizeof(d));memset(vis,0,sizeof(vis));        int ans=dp1(S);        printf("%d\n",ans);return 0; } </span>

如果使用递推求解呢??  那么问题非常类似完全背包。。简单很多

上面只求出问题的解。

那么扩展一下,如果有相同的解,组合不一样,这时要求最小字典序列?该怎么求?

举个例子理解一下。  S=12,硬币 2 4 5  那么求最多硬币组合数。答案是:3.。  有两种可能 4 4 4  和 2 5 5  那么最小字典序列式是后者。


<span style="font-size:24px;">#include"stdio.h"#include"stdlib.h"#include"algorithm"#include"string.h"#include"math.h"using namespace std;const int INF=1e9; const int max_coin=105;const int max_S=10005;int coin[max_coin];int maxx[max_S],minn[max_S];//maxx[i]  货币为总额i时,可以有最多种货币组成。 int n,S;void Solve(){//这里没有处理 找不着的情况,很简单。 maxx[0]=minn[0]=0;    for(int i=1;i<=S;i++)    {    maxx[i]=-INF;    minn[i]=INF;}for(int i=0;i<n;i++)  for(int j=coin[i];j<=S;j++)  {    maxx[j]=max(maxx[j],maxx[j-coin[i]]+1);    minn[j]=min(minn[j],minn[j-coin[i]]+1);  } // for(int i=0;i<=S;i++) // printf("%d %d\n",maxx[i],minn[i]);}void Print_path(int *d,int S){for(int i=0;i<n;i++)  if(S>=coin[i]&&d[S]==d[S-coin[i]]+1)    {  printf("%d ",i);  Print_path(d,S-coin[i]);  break;    }} int main(){scanf("%d%d",&n,&S);for(int i=0;i<n;i++)  scanf("%d",&coin[i]);Solve();printf("%d\t%d\n",maxx[S],minn[S]);Print_path(minn,S);return 0;}</span>


很多玩家喜欢这样输出打印(也是牺牲空间换取时间,易读性)


<span style="font-size:24px;">#include"stdio.h"#include"stdlib.h"#include"algorithm"#include"string.h"#include"math.h"using namespace std;const int INF=1e9; const int max_coin=105;const int max_S=10005;int coin[max_coin];int max_path[max_S]; int min_path[max_S];//max_path[S] 记录满足minn[S]=minn[S-coin[i]]+1的最小i; int maxx[max_S],minn[max_S];int n,S;void Solve(){maxx[0]=minn[0]=0;    for(int i=1;i<=S;i++)    {    maxx[i]=-INF;    minn[i]=INF;}memset(max_path,0,sizeof(max_path));memset(min_path,0,sizeof(min_path));for(int i=1;i<=n;i++)  for(int j=coin[i];j<=S;j++)  {    if(maxx[j]<maxx[j-coin[i]]+1)       {         maxx[j]=maxx[j-coin[i]]+1;         max_path[j]=i;   }    if(minn[j]>minn[j-coin[i]]+1)    {      minn[j]=minn[j-coin[i]]+1;      min_path[j]=i;  }  }}void Print_ans(int *d,int S){//路径逆序      while(S)    {    printf("%d ",d[S]);    S-=coin[d[S]];}} int main(){scanf("%d%d",&n,&S);for(int i=1;i<=n;i++)  scanf("%d",&coin[i]);Solve();printf("%d\t%d\n",maxx[S],minn[S]);Print_ans(min_path,S);return 0;}</span>


这里补充一个问题:

N元钱换为零钱,有多少不同的换法?币值包括1 2 5分,1 2 5角,1 2 5 10 20 50 100元。

例如:5分钱换为零钱,有以下4种换法:
1、5个1分
2、1个2分3个1分
3、2个2分1个1分
4、1个5分
(由于结果可能会很大,输出Mod 10^9 + 7的结果)

<span style="font-size:24px;">#include"stdio.h"#include"stdlib.h"#include"algorithm"#include"string.h"#include"math.h"int coin[14]={1,2,5,10,20,50,100,200,500,1000,2000,5000,10000};const int maxn=1e5+5;const int mod=1e9+7;int dp[maxn];//dp[i][j]表示前i种货币能够凑齐j钱组合数 int main(){ int n; scanf("%d",&n); dp[0]=1; for(int i=0;i<13;i++)    for(int j=coin[i];j<=n;j++)      dp[j]=(dp[j]%mod+dp[j-coin[i]]%mod)%mod; printf("%d\n",dp[n]); return 0; } </span>

 




1 0