硬币问题——紫书p261(递归,递推与最小字典序输出)

硬币问题
有n种硬币,面值分别V1,V2,V3,…,Vn,每种有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。
1<=n<=100 0<=S<=10000 1<=Vi<=S

解题思路
最长路与最短路问题

int dp_max(int S,int v[],int n)
{
    int *ans=&dma[S];  //ans和dma共为一个变量 c++用法:int &ans=dma[S]
    int i;
    if(*ans!=-1) return *ans;
    *ans=-(2<<29);    //让无法取得0的情况不影响结果
    for(i=0;i<n;i++)
        if(S>=v[i])
        *ans=Max(*ans,dp_max(S-v[i],v,n)+1);
    return *ans;
}

要注意保存最长路径的数组dma不能为0因为有的路径长度是可以为0的,如dma【0】,所以dma要初始化为-1,dma[0]初始化为0.
还要注意ans:ans不能为0或过大的数,若是无法达到最终状态0但是路径过长的话可能会影响结果,所以我们ans的初始化值要很小,不能影响到结果(求最短路同理,只不过ans要很大罢了)

递归解法:

#include <stdio.h>
#include <stdlib.h>
#define Max(a,b) (a)>(b)?(a):(b)
#define Min(a,b) (a)<(b)?(a):(b)
#define N 103
#define M 10003
int dma[M],dmi[M];

int dp_max(int S,int v[],int n)
{
    int *ans=&dma[S];  //ans和dma共为一个变量 c++用法:int &ans=dma[S]
    int i;
    if(*ans!=-1) return *ans;
    *ans=-(2<<29);    //让无法取得0的情况不影响结果
    for(i=0;i<n;i++)
        if(S>=v[i])
        *ans=Max(*ans,dp_max(S-v[i],v,n)+1);
    return *ans;
}

int dp_min(int S,int v[],int n)
{
    int i;
    int *ans=&dmi[S];
    if(*ans!=-1)
        return *ans;
    *ans=(2<<29);     //为了让无法取得0的情况不影响结果
    for(i=0;i<n;i++)
        if(S>=v[i])
         *ans=Min(*ans,dp_min(S-v[i],v,n)+1);
    return *ans;
}

int main()
{
    int n,i,S;
    int v[N];
    scanf("%d%d",&n,&S);
    for(i=0;i<n;i++)
        scanf("%d",&v[i]);
    for(i=1;i<=S;i++)   //初始化注意d[0]=0
    {
        dma[i]=-1;
        dmi[i]=-1;
    }
    printf("max:%d min:%d\n",dp_max(S,v,n),dp_min(S,v,n));
    return 0;
}

递推解法:
动态转移方程 更新状态方程:dp【i】=max/min(dp【i】,dp【i-v【i】】+1)

#include <stdio.h>
#include <stdlib.h>
#define INF (2<<29)
#define M 10003
#define N 103
#define Max(a,b) (a)>(b)?(a):(b)
#define Min(a,b) (a)<(b)?(a):(b)

int main()
{
    int minv[M],maxv[M],i,j;
    minv[0]=maxv[0]=0;
    int S,n,v[N];
    scanf("%d%d",&n,&S);
    for(i=0;i<n;i++)
        scanf("%d",&v[i]);

    for(i=1;i<=S;i++)
    {
        minv[i]=INF;
        maxv[i]=-INF;    //防止无法达到最终情况的路径影响结果
    }

    for(i=1;i<=S;i++)
        for(j=0;j<n;j++)
    {
        if(i>=v[j])
        {
            maxv[i]=Max(maxv[i],maxv[i-v[j]]+1);
            minv[i]=Min(minv[i],minv[i-v[j]]+1);
        }
    }

    printf("max:%d min:%d",maxv[S],minv[S]);
    return 0;
}

递归最小字典序:

#include <stdio.h>
#include <stdlib.h>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
#define N 103
#define M 10003
int dma[M],dmin[M];
int v[N],n;

int dp_max(int S)
{
    int *ans=&dma[S],i;
    if(*ans!=-1) return *ans;
    *ans=-(2<<30);
    for(i=0;i<n;i++)
        if(S>=v[i])
            *ans=Max(*ans,dp_max(S-v[i])+1);
    return *ans;
}

int dp_min(int S)
{
    int *ans=&dmin[S],i;
    if(*ans!=-1)
        return *ans;
    *ans=2<<29;
    for(i=0;i<n;i++)
        if(S>=v[i])
         *ans=Min(*ans,dp_min(S-v[i])+1);
    return *ans;
}

void printf_ans_max(int S)
{
    int i;
    for(i=0;i<n;i++)
    {
        if(S>=v[i] && dma[S]==dma[S-v[i]]+1)   //d[S]不会为负数故不会找到无法到最终情况结果
      {
        printf("%d ",v[i]);
        printf_ans_max(S-v[i]);
        break;    //防止回溯后继续遍历
      }
    }
}

void printf_ans_min(int S)
{
    int i;
    for(i=0;i<n;i++)
    {
        if(S>=v[i] && dmin[S-v[i]]+1==dmin[S])
        {
            printf("%d ",v[i]);
            printf_ans_min(S-v[i]);
            break;
        }
    }
}

int main()
{
   int S,i;
   while(~scanf("%d%d",&n,&S))
   {
       for(i=1;i<=S;i++)
         dma[i]=dmin[i]=-1;  //初始化
       for(i=0;i<n;i++)
        scanf("%d",&v[i]);
        printf("max:%d  min:%d\n",dp_max(S),dp_min(S));
        printf("max:");
        printf_ans_max(S);
        putchar('\n');
        printf("min:");
        printf_ans_min(S);
        putchar('\n');
   }
   return 0;
}

动态规划 最小字典序

#include <stdio.h>
#include <stdlib.h>
#define M 10003
#define N 103
int maxv[M],minv[M];
int max_coin[M],min_coin[M];
int v[N];

void printf_ans(int S,int *coin)
{
    while(S)
    {
        printf("%d ",v[coin[S]]);
        S-=v[coin[S]];
    }
}

int main()
{
    int S,n,i,j;
    while(~scanf("%d%d",&n,&S))
    {
        for(i=1;i<=S;i++)
        {
            maxv[i]=-(2<<29);
            minv[i]=(2<<29);
        }

        for(i=0;i<n;i++)
            scanf("%d",&v[i]);

        for(i=1;i<=S;i++)
            for(j=0;j<n;j++)
        {
            if(i>=v[j])
            {
                if(maxv[i]<maxv[i-v[j]]+1)
                {
                    maxv[i]=maxv[i-v[j]]+1;
                    max_coin[i]=j;
                }
                if(minv[i]>minv[i-v[j]]+1)
                {
                    minv[i]=minv[i-v[j]]+1;
                    min_coin[i]=j;
                }
            }
        }
        printf("max:%d  min:%d\n",maxv[S],minv[S]);
        printf("max:");
        printf_ans(S,max_coin);
        putchar('\n');
        printf("min:");
        printf_ans(S,min_coin);
        putchar('\n');
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值