Coins(多重背包方案可行性dp + 优化)

Coins

题目

给出硬币面额及每种硬币的个数,求从1到m能凑出面额的个数。

思路

1.朴素的多重背包

题面给出的很明显的多重背包,定义dp为考虑前i种硬币,能凑出j元的方案可行性,可以得到第一版代码

O(nm^2) 代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define debug(x) printf("x--->%lld\n",x)
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5, mod = 1e9 + 7;
int n,m;
bool dp[110][N];
int a[110],b[1000];// amax = 1e5 bmax = 1000
void solve()
{    
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    memset(dp,0,sizeof dp);
    dp[0][0] = 1;
    for(int i=1;i<=n;i++)
        for(int j=0; j<=m;j++)
        {
            int v=a[i];
            dp[i][j] = dp[i-1][j];//已经在之前凑出
            if(dp[i][j])continue;
            for(int k=0;k<=b[i] && j-v*k>=0;k++)// 查找要几枚才可能凑出j
                if(dp[i-1][j-v*k])
                {
                    dp[i][j]=1;
                    break;
                } 
        }
    // for(int i=1;i<=m;i++)cout<<dp[n][i]<<' ';cout<<endl;
    ll ans = 0 ;
    for(int j=1;j<=m;j++)ans+= (dp[n][j]!=0);
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();
    while(cin>>n>>m,(n||m))
    solve();

    return 0;
}

显然上面的代码时间复杂度是无法接受的,考虑到dp是bool型,事实上丢掉了

很多信息,所以考虑修改dp定义,增加dp记录的信息,优化时间。

2.时间优化

我们定义 dp 为考虑前i种硬币凑出j元的所有方案的集合,而其中储存第i种硬币的凑出j后的剩余数量,并规定,dp[i][j[] = -1 ,代表无法凑出 j。
通过上述定义,如果 dp[i][j] = -1 表示无法凑出j ,其余表示方案可行,一样可以得到本题答案。
同时,对于dp[i][j] 可以确定是从 dp[i-1][j](考虑i-1种,凑出j元) 和 dp[i][j-v](考虑i种,还差一个第i种硬币就凑出 j ,这两个状态转移过来,这样就不用再暴力查找j 的前状态来确定 dp[i][j] 了

状态转移:

  • dp[i][j] <== dp[i-1][j] or dp[i-1][k]
  • dp[i][j] <== dp[i-1][j] or dp[i][j-vi]

成功把下面的循环优化掉

 for(int k=0;k<=b[i] && j-v*k>=0;k++)// 查找要几枚才可能凑出j
                if(dp[i-1][j-v*k])
                {
                    dp[i][j]=1;
                    break;
                }

3.空间优化

本题卡空间,所以还要把二维dp优化为一维,较容易解决

  • 对于 dp[i-1][j] => dp[i][j] <==> dp[j] (old) ⇒ dp[j] (new)
  • 对于 dp[i][j-vi] => dp[i][j] <==> dp[j-vi] (new) ⇒ dp[j] (new)

最终代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define debug(x) printf("x--->%lld\n",x)
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5, mod = 1e9 + 7;
int n,m;
int dp[N];// dp[110][N];
int a[110],b[1000];// amax = 1e5 bmax = 1000
void solve()
{    
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    memset(dp,-1,sizeof dp);
    dp[0] = 0;// 规定 dp[0] 合法
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            // 朴素空间做法 与下面相等价
            /* if(dp[i-1][j]>=0)
                dp[i][j] = b[i];
            else
            {
                if(j-a[i]>=0 && dp[i][j-a[i]]>0)
                    dp[i][j] = dp[i][j-a[i]] - 1;
                else dp[i][j] = -1;
            } */
            if(dp[j]>=0)// 已经凑齐j 第i种硬币全部剩下
                dp[j] = b[i];
            else
            {  // 未凑齐
                if(j-a[i] >=0 && dp[j-a[i]] > 0) // 合法 且 仍有硬币剩余
                    dp[j]  = dp[j-a[i]] -1;
            }
            // 剩下未更新的即无法凑出 不合法
        }
    ll ans = 0;
    for(int i=1;i<=m;i++) ans += dp[i]>=0;
    cout << ans <<endl;
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();
    while(cin>>n>>m,n||m)
    solve();

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值