【多重背包】A000_AW_硬币(贪心+dp)

给定N种硬币,其中第 i 种硬币的面值为Ai,共有Ci个。

从中选出若干个硬币,把面值相加,若结果为S,则称“面值S能被拼成”。

求1~M之间能被拼成的面值有多少个。

输入格式
输入包含多组测试用例。
每组测试用例第一行包含两个整数N和M。
第二行包含2N个整数,分别表示A1,A2,…,AN和C1,C2,…,CN。
当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。

输出格式
每组用例输出一个结果,每个结果占一行。

数据范围
1≤N≤100,
1≤M≤10^5,
1≤Ai≤10^5,
1≤Ci≤1000

输入用例:
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
输出用例:
8
4

方法一:贪心+dp

  • 定义状态
    • f[i]=0/1 表示面值为 i 的硬币能/否凑出
    • g[j] 表示凑出面值为 j 的硬币时,使用面值为 v[i] 的次数,这里因为每个硬币都有使用次数限制,故应尽量先使用完一种再用其它硬币(贪心)
  • 思考初始化:
    • f[0]=1
    • g[...]=0
  • 思考状态转移方程
    • f[j]=f[j-v[i]];g[j]=g[j-v[i]]+1,if (!f[j] && f[j-A[i]] && g[j-v[i]<C[i])
  • 思考输出:...
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
    
int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,m; 
    while (true) {
        cin>>n>>m; if (n==0 && m==0) break;
        ll A[n], C[n];
        for (int i=0; i<n; i++) cin>>A[i];
        for (int i=0; i<n; i++) cin>>C[i];
        int f[m+5], g[m+5]; memset(f, false, sizeof f);
        f[0]=1;
        for (int i=0; i<n; i++) {
            int x=A[i]; memset(g, 0, sizeof g);
            for (int j=x; j<=m; j++) if (!f[j] && f[j-A[i]] && g[j-x]<C[i]) {
                f[j]=true, g[j]=g[j-x]+1;
            }
        }
        ll ans=0;
        for (int i=1; i<=m; i++) if (f[i])
            ans++;
        cout << ans << '\n';
    }
    return 0;
}

复杂度分析

  • Time\(O(nm)\)
  • Space\(O(n+m)\)
原文地址:https://www.cnblogs.com/wdt1/p/13598926.html