【多重背包】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)\)