题目大意:
给定N个硬币,Ai表示第i个硬币的价值,Ci表示第i个硬币的数量。若从中选出若干个硬币把面值相加,设结果为S,则称“面值S能被拼成”。求1——M之间能拼成的面值有多少个。
解题思路:
dp
本题询问的是“可行性”(面值是否能被拼成)
设fj表示能拼成面值j,用直接拆分法直接求解会超时,我们发现:
前i-1中硬币就能拼成j,即在第i阶段开始前,变量F[j]=true。
使用了第i种硬币,即在第i阶段的递推中,发现f[j-a[i]]=true,从而变量F[j]变为true
我们可以考虑一种贪心策略:设F[j]表示f[j] 在阶段i时为true至少需要用多少枚第一种硬币,并且尽量选择第一类情况。也就是说,在f[j-a[i]]为true时,若果f[j]已经为true,则不执行Dp的转移,并令F[j]=F[j-a[i]]+1
Accepted code:
#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<cctype>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc c=getchar()
using namespace std;
int n,m,Ans,a[100001],c[100001];
inline void read(int &f) {
f=0; int ag=1; char gc;
while(!isdigit(c)) {if (c=='-') ag=-1; gc;}
while(isdigit(c)){f=(f<<3)+(f<<1)+c-48;gc;}
f*=ag; return;
}
bool init() {
read(n);read(m);
if (n&&m) {
for (int i=1;i<=n;i++) read(a[i]);
for (int i=1;i<=n;i++) read(c[i]);
} else return false;
return true;
}
void Dp() {
int F[m+1];bool f[m+1]; Ans=0;
memset(f,0,sizeof(f));
f[0]=true;
for (int i=1;i<=n;i++) {
for (int j=0;j<=m;j++) F[j]=0;
for (int j=a[i];j<=m;j++)
if (!f[j]&&f[j-a[i]]&&F[j-a[i]]<c[i]) {
f[j]=true; F[j]=F[j-a[i]]+1,Ans++;
}
}
return;
}
void write(int x) {
if (x>9) write(x/10); putchar(x%10+48); return;
}
void writeln(int x) {
write(x); putchar('\n');
}
int main() {
while (init()) {
Dp();
writeln(Ans);
}
}