题目描述
小Z在梦里梦见自己代表祖国参加了国际信息学奥林匹克竞赛并获得了金牌,颁奖会结束后他想到附近的超市去买些礼物带回国送给班里的小伙伴们,他心想这里的巧克力是食物中的王者,给小伙伴们每人买一盒巧克力肯定会大受欢迎。于是他一进超市就直奔装有巧克力的货架,一口气拿了40盒巧克力,然后到收银台去排队结帐。当轮到小Z结账时,他发现自己身上只有一张大面额的钞票了,于是他把这张钞票递了过去,收银员迅速在收银机上算出了要找给他的金额,然后打开钱柜准备找钱。小Z看到钱柜里的硬币按面额从大到小整整齐齐地摆放着,一种面额的硬币垒成一列,见此情景他给在座的各位出了一道题。
输入
输入数据第一行有两个用空格隔开的整数N和K,其中1≤N≤300,表示超市收银员要找给小Z的金额,1≤K≤8,表示收银员的钱柜里共有K种不同面额的硬币。
第2行包含K个正整数Ci,其中1≤Ci≤100在数据中硬币面额按降序排列。不同种类的硬币面额各不相同,每种硬币都取之不尽用之不竭。
输出
输出数据仅有一行包含一个整数,表示超市收银员可能的找零方案数。答案保证不会超出长整型(long,其实也就是int)范围。需要注意的是如果没有面额为1的硬币,有些金额将无法找零,此时 结果就输出0。
样例数据:
输入
83 5
50 25 10 5 1
输出 159
数据范围
对于10%的数据,N≤50,K≤3,Ci≤10;
对于30%的数据,N≤100,K≤5,Ci≤20;
对于60%的数据,N≤100,K≤7,Ci≤50;
对于100%的数据,N≤300,K≤8,Ci≤100。
题目思路
这道题目当我看到"取之不尽用之不竭"这几个字时,我就已经知道这是一道动态规划完全背包问题的水题了。
但考虑到这道题目数据范围较小,所以这道题目共分为两种思路。
第一种思路是用递归(深搜)解决。事实上,绝大多数背包问题都是用递归求解的(只不过是慢了一些)。用递归求解,就是一次性将所有状况全部搜索并比较一遍,最后求出结果。用递归写的难度偏大(但这还是一道水题)。用递归写的主程序如下:
int m,n,a[100001];
void dfs(int d,int s)
{
if(s>m) return;//不可或缺
if(d>n)
{if(m==s) ans++;}//要套括号
else
{
if(s+a[d]<=m) dfs(d,s+a[d]);
dfs(d+1,s);//递归
}
}
另外一种思路是用动态规划做。如果用动态规划写的话,这就是一道不折不扣的水题了(这不就是硬币找零问题吗?),只要会完全背包和背包的增维(一维数组变二维数组)就完全可以做出来。用动态规划写的主程序如下:
int f[305][100001];
int cx(int t[],int n,int m)
{
for(int i=0;i<=n;i++) f[i][0]=1;//预处理
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=t[i]) f[i][j]+=f[i][j-t[i]];
}
}//动规程序
return f[n][m];
}
题目标程
这道题目的标程如下。
递归标程:
#include<bits/stdc++.h>
#define maxn 100001
using namespace std;
int n,m,a[maxn],ans=0;
void dfs(int d,int s)
{
if(s>m) return;//不可或缺
if(d>n)
{if(m==s) ans++;}//要套括号
else
{
if(s+a[d]<=m) dfs(d,s+a[d]);
dfs(d+1,s);
}
}
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>a[i];
dfs(1,0);
cout<<ans;
return 0;
}
动规标程:
#include<bits/stdc++.h>
#define maxn 100001
#define minn 305
using namespace std;
int t[minn],f[minn][maxn],n,m;
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>t[i];
for(int i=0;i<=n;i++) f[i][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=t[i]) f[i][j]+=f[i][j-t[i]];
}
}
cout<<f[n][m];
return 0;
}
这道题目就这么多。