欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-13 07:57:58
...

题目来源:货币系统

题目描述

给定 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;
}