找钱问题

找钱问题描述:

与背包问题不同,找钱问题是结果必须是把容量全部装满

一.用的钱的最大最小数目

把空间开大,所需求的dp值只是其中的一种特殊情况而已

1.01背包模型,每种货币只能用一次
最小求法

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[50000],w[100];
const int MAX = 50000;
const int inf = 0x3f3f3f3f;
int n,m;
int main()
{
    while(~scanf("%d%d",&n,&m)){
        for(int i = 1;i <= n ; i++)
            scanf("%d%d",&w[i]);
        for(int i = 1; i <= MAX ;i++)
            dp[i] = inf;
        dp[0] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = MAX ; j >= w[i] ;j--){
                dp[j] = min(dp[j],dp[j-w[i]]+1);
            }
        }
        if(dp[m] == inf) printf("-1
");
        else printf("%d
",dp[m]);
    }
    return 0;
}
View Code

最大求法   只要将初始化变成-1,min改成max

2.完全背包模型,每种货币无限使用

把第二个for循环倒一下就行

3.多重背包模型,每一种货币都有数目限制

1.用完全背包和01背包

2.只用01背包*

用二进制来降低复杂度,应为所有的情况都可以用这些二进制的组合来表示,放大拆开,只要放大取出就行

最小求法,最大同理

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[50000],w[100],c[100];
const int MAX = 50000;
const int inf = 0x3f3f3f3f;
int n,m;
void oneback(int cost,int cnt)
{
    for(int i = MAX; i >= cost; i--)
        dp[i] = min(dp[i-cost]+cnt, dp[i]);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i = 1; i <= n ; i++)
            scanf("%d%d",&w[i],&c[i]);
        for(int i = 1; i <= MAX;i ++)
            dp[i] = inf;
        dp[0] =0;
        int k;
        for(int i = 1; i <= n ; i++){
            k = 1;
            while(k < c[i]){
                oneback(k*w[i],k);
                c[i] -= k;
                k*= 2;
            }
            oneback(w[i]*c[i],c[i]);
        }
        if(dp[m] == inf) printf("-1
");
        else printf("%d
",dp[m]);
    }
return 0;
}
View Code

4.多重背包模型,钱多了要找回(找回的时候是完全背包,不限数目)

最小求法

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[50000],w[100],c[100];
const int MAX = 50000;
const int inf = 0x3f3f3f3f;
int n,m;
void oneback(int cost,int cnt)
{
    for(int i = MAX; i >= cost; i--)
        dp[i] = min(dp[i-cost]+cnt, dp[i]);
}
void comback(int cost,int cnt)
{
    for(int i = MAX+cost ; i >= 0 ; i--)
        dp[i] = min(dp[i-cost]+cnt,dp[i]);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i = 1; i <= n ; i++)
            scanf("%d%d",&w[i],&c[i]);
        for(int i = 1; i <= MAX;i ++)
            dp[i] = inf;
        dp[0] =0;
        int k;
        for(int i = 1; i <= 2*n ; i++){//这样写方便,没有什么特殊的含义。。
            if(i <= n){
            k = 1;
            while(k < c[i]){
                oneback(k*w[i],k);
                c[i] -= k;
                k*= 2;
            }
            oneback(w[i]*c[i],c[i]);
            }
        }
        else comback(-w[i-n],1);
    }
        if(dp[m] == inf) printf("-1
");
        else printf("%d
",dp[m]);
    }
return 0;
}
View Code

 完全背包是从小到大的,但是负数,把循环方向改变一下。循环条件的该表只是要适应下面dp值的改变

5.记录路径的多重背包模型(完全背包和01背包只要把if条件改一下)

最大求法

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[10000],w[10000],num[10000],t[10000],path[10000],ans[10000];
const int MAX = 50000;
const int inf = 0x3f3f3f3f;
int n,m;
int main()
{
    while(~scanf("%d%d",&n,&m)){
        for(int i = 1; i <= n ; i++)
            scanf("%d%d",&w[i],&t[i]);
        memset(dp,0,sizeof(dp));
        dp[0] = 1;
        for(int i = 1;  i <= n ; i++){
            for(int j = w[i]; j <= m; j++){
                if(dp[j-w[i]] && dp[j-w[i]] + 1 > dp[j] && num[j-w[i]] < t[i]){
                    dp[j] = dp[j-w[i]];
                    num[j] = num[j-w[i]] + 1;
                    path[j] = j - w[i];
                }
            }
        }
        int i = m;
        if(dp[m] > 0){
            while(i!=0){
                ans[i-path[i]]++;
                i = path[i];
            }
           for(int i = 1; i <= n ;i++){
               if(ans[i]){
                printf("%d:%d ",i,ans[i]);
               }
           }
        }
    }
    return 0;
}
View Code

path记录的是当前有j钱的时候买了一件东西之后的最优路径,因为完全背包最后解肯定是最优的所以倒推是正确的

dp只是用来判断,那个值并不能用

最小求法

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int v[5] = {0,1,5,10,25};
int dp[10010],ans[10010],num[10010],path[10010],t[5];
int p;
const int inf = 0x3f3f3f3f;
int main()
{
    while(~scanf("%d",&p)){
        for(int i = 1; i <= 4;  i++)
            scanf("%d",&t[i]);
        if((p+t[1]+t[2]+t[3]+t[4]) == 0) break;
        memset(ans,0,sizeof(ans));
        memset(path,0,sizeof(path));
        for(int i = 1; i <= 10010; i++)
            dp[i] = inf;
        dp[0] = 1;
        for(int i = 1; i <= 4; i++){
            memset(num,0,sizeof(num));
            for(int j = v[i]; j <= p; j++){
                if(dp[j-v[i]] != inf && dp[j-v[i]] + 1 < dp[j] && num[j-v[i]] < t[i]){
                    dp[j] = dp[j-v[i]] + 1;//使得后面每一个状态都是从前面一个得到,并且满足两个条件1:用去一个后的数目要比没用去的多2:用去的硬币数目不超过硬币本身
                    num[j] = num[j-v[i]] + 1;
                    path[j] = j - v[i];//难想到。。用来记录路径
                }
            }
        }
        int i = p;
        if(dp[p]!=inf){
            while(i!=0){
                ans[i-path[i]]++;//i-path[i] = i - ( i - v[i]) = v[i]
                i = path[i];//path[i]表示有i钱的时候用了一种硬币之后的钱的数目
            }
            printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.
",ans[1],ans[5],ans[10],ans[25]);
        }
        else printf("Charlie cannot buy coffee.
");
    }
    return 0;
}
View Code

6.

  

原文地址:https://www.cnblogs.com/zero-begin/p/4470752.html