【acwing 寒假每日一题(入门组)】day15货币系统

题目来源:货币系统

题目描述

给定 V 种货币(单位:元),每种货币使用的次数不限。

不同种类的货币,面值可能是相同的。

现在,要你用这 V 种货币凑出 N 元钱,请问共有多少种不同的凑法。

输入格式
第一行包含两个整数 V 和 N。

接下来的若干行,将一共输出 V 个整数,每个整数表示一种货币的面值。

输出格式
输出一个整数,表示所求总方案数。

数据范围
1≤V≤25,
1≤N≤10000
答案保证在long long范围内。

输入样例:
3 10
1 2 5
输出样例:
10

思路

典型的完全背包问题,三个版本慢慢推进,思路看代码的详细注释

代码

朴素做法

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=30,M=10010;

int n,m;
int v[N];
LL f[N][M];

/*
    状态定义:f[i][j]表示用i种货币凑出面置j的方案的数量
    属性:数量
    状态转移(该怎么表示当前状态 或者是当前状态的集合可能怎么划分):
        可以使用第i中货币使用的次数来划分
        使用0次、1次、、、n次 但是n必须满足 n*v[i]<=j 即不能大于需要我们凑的钱
        使用0次指的是,用前i-1个货币凑出面置j,按照我们定义就是f[i-1][j]
        使用1次指的是,用前i-1个货币凑出面置j-v[i],按照我们定义就是f[i-1][j-v[i]]
        ...
        使用n次指的是,用前i-1个货币凑出面置j-n*v[i],按照我们定义就是f[i-1][j-n*v[i]]
        
        f[i][j]=f[i-1][j]+f[i-1][j-v[i]+...+f[i-1][j-n*v[i]]
*/

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i];
    
    f[0][0]=1; //初始状态,0种货币凑出面置0,一种方案
    for(int i=1;i<=n;i++) //使用i种货币
        for(int j=0;j<=m;j++) //凑出面置j
            for(int k=0;k*v[i]<=j;k++) //使用第i种货币k次数
            {
                f[i][j]+=f[i-1][j-k*v[i]]; //累加方案数
            }
    cout<<f[n][m]<<endl; //结果就是 使用n种货币凑出面置m的方案数
    return 0;
}

转移方程优化

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=30,M=10010;

int n,m;
int v[N];
LL f[N][M];

/*
    状态定义:f[i][j]表示用i种货币凑出面置j的方案的数量
    属性:数量
    状态转移(该怎么表示当前状态 或者是当前状态的集合可能怎么划分):
        可以使用第i中货币使用的次数来划分
        使用0次、1次、、、n次 但是n必须满足 n*v[i]<=j 即不能大于需要我们凑的钱
        使用0次指的是,用前i-1个货币凑出面置j,按照我们定义就是f[i-1][j]
        使用1次指的是,用前i-1个货币凑出面置j-v[i],按照我们定义就是f[i-1][j-v[i]]
        ...
        使用n次指的是,用前i-1个货币凑出面置j-n*v[i],按照我们定义就是f[i-1][j-n*v[i]]
        
        f[i][j]=f[i-1][j]+f[i-1][j-v[i]+...+f[i-1][j-n*v[i]]
        
        
        
        我们惊人的发现:
        f[i][j-v[i]]=f[i-1][j-v[i]]+f[i-1][j-2*v[i]]+...+f[i-1][j-n*v[i]]
        也就是说 f[i][j]=f[i-1][j]+f[i][j-v[i]]
*/

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i];
    
    f[0][0]=1; //初始状态,0种货币凑出面置0,一种方案
    for(int i=1;i<=n;i++) //使用i种货币
        for(int j=0;j<=m;j++) //凑出面置j
        {
            //下面是转移方程,但是需要j>=v[i] 不然肯定不成立
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]+=f[i][j-v[i]];
        }
    cout<<f[n][m]<<endl; //结果就是 使用n种货币凑出面置m的方案数
    return 0;
}

空间优化

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=30,M=10010;

int n,m;
int v[N];
LL f[M];

/*
    状态定义:f[i][j]表示用i种货币凑出面置j的方案的数量
    属性:数量
    状态转移(该怎么表示当前状态 或者是当前状态的集合可能怎么划分):
        可以使用第i中货币使用的次数来划分
        使用0次、1次、、、n次 但是n必须满足 n*v[i]<=j 即不能大于需要我们凑的钱
        使用0次指的是,用前i-1个货币凑出面置j,按照我们定义就是f[i-1][j]
        使用1次指的是,用前i-1个货币凑出面置j-v[i],按照我们定义就是f[i-1][j-v[i]]
        ...
        使用n次指的是,用前i-1个货币凑出面置j-n*v[i],按照我们定义就是f[i-1][j-n*v[i]]
        
        f[i][j]=f[i-1][j]+f[i-1][j-v[i]+...+f[i-1][j-n*v[i]]
        
        
        
        我们惊人的发现:
        f[i][j-v[i]]=f[i-1][j-v[i]]+f[i-1][j-2*v[i]]+...+f[i-1][j-n*v[i]]
        也就是说 f[i][j]=f[i-1][j]+f[i][j-v[i]]
        
        
        接着我们对空间进行优化
         f[i][j]=f[i-1][j]+f[i][j-v[i]] 变为了f[j]=f[j]+f[j-v[i]]
         直接将前面一维删掉了,为什么可以这样呢?
         其实如果是f[i][j]=f[i][j]+f[i][j-v[i]],我们肯定会直接删掉一维的,但是他是i-1
         其实是直接删掉也是不影响的,
         因为我们当前计算的是f[j],所以式子右边的f[j]肯定是之前的数据,
         而f[j-v[i]]比f[j]小,我们是从前往后计算的,那么这个f[j-v[i]]肯定是新的数据
*/

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i];
    
    f[0]=1; //初始状态,0种货币凑出面置0,一种方案
    for(int i=1;i<=n;i++) //使用i种货币
        for(int j=v[i];j<=m;j++) //凑出面置j
        {
            f[j]=f[j]+f[j-v[i]];
        }
    cout<<f[m]<<endl; //结果就是 使用n种货币凑出面置m的方案数
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值