【DP | 记忆化搜索】【ICPC 2017 HongKong 】Optimal Coin Change

题目描述

In a 10-dollar shop, everything is worthy 10 dollars or less. In order to serve customers more effectively at the cashier, change needs to be provided in the minimum number of coins.
In this problem, you are going to provide a given value of the change in different coins. Write a program to calculate the number of coins needed for each type of coin.
The input includes a value v, a size of the coinage set n, and a face value of each coin, f1, f2, ..., fn. The output is a list of numbers, namely, c1, ..., cn, indicating the number of coins needed for each type of coin. There may be many ways for the change. The value v is an integer satisfying 0 < v ≤ 2000, representing the change required
in cents. The face value of a coin is less than or equal to 10000. The output of your program should take the combination with the least number of coins needed.
For example, the Hong Kong coinage issued by the Hong Kong Monetary Authority consists of 10 cents, 20 cents, 50 cents, 1 dollar, 2 dollars, 5 dollars and 10 dollars would be represented in the input by n = 7, f1 = 10, f2 = 20, f3 = 50, f4 = 100, f5 = 200, f6 = 500, f7 = 1000.


输入

The test data may contain many test cases, please process it to the end of the file.
Each test case contains integers v, n, f1, ..., fn in a line. It is guaranteed that n ≤ 10 and 0 < f1 < f2 < ...< fn.


输出

The output be n numbers in a line, separated by space. If there is no possible change, your output should be a single −1. If there are more than one possible solutions, your program should output the one that uses more coins of a lower face value.


样例输入

 

2000 7 10 20 50 100 200 500 1000
250 4 10 20 125 150
35 4 10 20 125 150
48 4 1 8 16 20
40 4 1 10 13 37
43 5 1 2 21 40 80

样例输出

0 0 0 0 0 0 2
0 0 2 0
-1
0 1 0 2
3 0 0 1
1 1 0 1 0

【题意】:

给你v,n,n种硬币的面值,然后问,找零的值为:V,n种面值的硬币,怎样找零才能使得找的个数最少。

【题解】:

DAG最短路,紫书上P262-266的经典例题,不仅是求最少的个数,还需要求记录路劲。

这个题目怎么分析呢???

我的做法是类似于完全背包一样,进行“刷表法”,这个一般不叫作 状态转移方程。

我们很明显知道这个表:dp[ x ] =min ( dp [ x -  a[ i ] ]+ 1 ,dp [ x ] )

 然后进行逆推,用贪心的思想往前延伸,因为用的面值越大,那么路径越短。

所以多写一个while往前延伸。

#include<bits/stdc++.h>
using namespace std;
const int N=2200;
const int M=15;
int dp[N],vis[M];
int main()
{
    int V,n,a[M];
    while(scanf("%d%d",&V,&n)!=EOF){
        memset(dp,127,sizeof(dp));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++)   scanf("%d",&a[i]);
        dp[0]=0;
        for(int i=1;i<=n;i++)
            for(int j=a[i] ;j<=V;j++)
                dp[j]=min(dp[j],dp[j-a[i]]+1);
        int cur=V;
        if(dp[V]==dp[2001]){
            printf("-1\n");
            continue;
        }
        while(cur)
            for(int i=n;i>=1;i--)
                if( cur-a[i]>=0 && dp[cur]==dp[cur-a[i]]+1){
                    vis[i]++;
                    cur-=a[i];
                    break;
                }
        for(int i=1;i<=n;i++)
            printf("%d%c",vis[i],i==n?'\n':' ');
    }
    return 0;
}

 

紫书上的写法:

#include<bits/stdc++.h>
using namespace std;
const int N=2020;
const int M=20;
int d[N],v[M],vis[M],road[N],n,V;

void init(){
    memset(d,127,sizeof(d));
    memset(vis,0,sizeof(vis));
    d[0]=0;
}

void print_ans(int *d,int S){
    while(S){
        vis[d[S]]++;
        S-=v[d[S]];
    }
}
int main()
{
    while(~scanf("%d%d",&V,&n)){
        init();
        for(int i=1;i<=n;i++)   scanf("%d",&v[i]);
        /*for(int i=1;i<=V;i++)
            for(int j=1;j<=n;j++)
                if(i>=v[j])
                    d[i]=min(d[i],d[i-v[j]]+1);
        */
        for(int i=1;i<=V;i++)
            for(int j=1;j<=n; j++)
                if(i>=v[j] && d[i]>d[i-v[j]]+1){
                    d[i]=d[i-v[j]]+1;
                    road[i]=j;
                }
        if(!road[V]){
            puts("-1");continue;
        }

        print_ans(road,V);
        for(int i=1;i<=n;i++)
            printf("%d%c",vis[i],i==n?'\n':' ');

    }
    return 0;
}

关于记录路径有两个状态:

状态一:初始化时候用特殊值:

void init(){
    memset(d,-1,sizeof(d));
    memset(vis,0,sizeof(vis));
    d[0]=0;
}
int dp(int S){
    int &ans = d[S];
    if(ans!=-1) return ans;
    ans = (1<<30);          //如果为最长路,则赋值为-(1<<30)
    for(int i=n;i>=1;i-- ) if(S>=v[i]) ans=min(ans,dp(S-v[i])+1); 
    // for 循环,[1,n] , 对应的min,改为max
    return ans;
}

void print_ans(int S){
    for(int i=n;i>=1;i--){
        //for循环改为 [1,n]
        if(S>=v[i] && d[S]==d[S-v[i]]+1){
            vis[i]++;
            print_ans(S-v[i]);
            break;
        }
    }
}

状态二:直接用是否被标记来界定:

void init(){
    memset(d,127,sizeof(d));
    memset(vis,0,sizeof(vis));
    memset(Ans,0,sizeof(Ans));
    d[0]=0;vis[0]=1;
}
int dp(int S){
    int &ans = d[S];
    if(vis[S]) return ans;
    vis[S]=1;
    //如果为最长路,则赋值为-(1<<30)
    for(int i=n;i>=1;i-- ) if(S>=v[i]) ans=min(ans,dp(S-v[i])+1);
    // for 循环,[1,n] , 对应的min,改为max
    return ans;
}

void print_ans(int S){
    for(int i=n;i>=1;i--){
        //for循环改为 [1,n]
        if(S>=v[i] && d[S]==d[S-v[i]]+1){
            Ans[i]++;
            print_ans(S-v[i]);
            break;
        }
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值