题目链接:L3-001 凑零钱
梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有 10^4
枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。
输入格式:
输入第一行给出两个正整数:N(≤10^ 4)是硬币的总个数,M(≤10^ 2)是韩梅梅要付的款额。第二行给出 N 枚硬币的正整数面值。数字间以空格分隔。
输出格式:
在一行中输出硬币的面值 V1≤V2≤⋯≤Vk,满足条件 V1+V2+…+Vk=M。数字间以 1 个空格分隔,行首尾不得有多余空格。若解不唯一,则输出最小序列。若无解,则输出 No Solution。
注:我们说序列{ A[1],A[2],⋯ }比{ B[1],B[2],⋯ }“小”,是指存在 k≥1 使得 A[i]=B[i] 对所有 i<k 成立,并且 A[k]<B[k]。
输入样例 1:
8 9
5 9 8 7 2 3 4 1
输出样例 1:
1 3 5
输入样例 2:
4 8
7 2 4 3
输出样例 2:
No Solution
题意很明确,我们很容易想到使用dfs进行最优解的搜索。
要保证是最优解,就必须尽可能先取小的,通过升序排序即可。
搜索过程中,记录当前已经搜索的零钱值和位置。
dfs暴力求解:(两个点超时)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m,a[N],t,num,ans[N],cnt;
//now是当前的零钱数量,idx是现在的标号
void dfs(int now,int idx) {
if(now==m) {
//此时肯定是最优解,因为我们是升序排列的,
//此时必定是最小序列,可以直接输出。
for(int i=0; i<cnt; i++) {
if(i) cout<<" ";
cout<<ans[i];
}
exit(0);//直接结束程序
}
//剪枝,舍去肯定不是最优解的情况,同时防止越界
if(now>m||idx>=num) return;
for(int i=idx+1; i<num; i++) {
ans[cnt++]=a[i];
dfs(now+a[i],i);
cnt--;//可以选,可以不选,回溯
}
}
int main() {
scanf("%d%d",&n,&m);
while(n--) {
scanf("%d",&t);
if(t<=m) a[num++]=t;
}
sort(a,a+num);
dfs(0,-1);
cout<<"No Solution";
return 0;
}
测试结果:
让我们想想还能怎么样优化?
//在下面这段代码中,我们是不是都要进行一昧的dfs呢,
//因为金钱是升序排序的,如果当前这个加上之前的都已经超过了总额,
//那么后面的肯定也会超过,所以这里可以特判然后返回!
for(int i=idx+1; i<num; i++) {
ans[cnt++]=a[i];
dfs(now+a[i],i);
cnt--;
}
//再次剪枝后:
for(int i=idx+1; i<num; i++) {
if(now+a[i]>m) { //剪去肯定不满足解的情况
return ;
}
ans[cnt++]=a[i];
dfs(now+a[i],i);
cnt--;
}
剪枝后:
哈哈,只差一分了,我们还能怎么优化呢?由于dfs不具有记忆性,所以我们不能通过记忆化搜索的方式进行优化。dfs函数看来是动不了了。
我们考虑一下最极端的情况,或者说分析超时最可能的原因,无非就是dfs调用太多了。什么导致的呢?极有可能就是一直找不到解!(有解必终止)。于是我们可以试试预处理所有的金钱和,看是否能够凑够m,不行直接输出No Solution。
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m,a[N],t,num,ans[N],cnt;
//now是当前的零钱数量,idx是现在的标号
void dfs(int now,int idx) {
if(now==m) {
for(int i=0; i<cnt; i++) {
if(i) cout<<" ";
cout<<ans[i];
}
exit(0);
}
if(now>m||idx>=num) return;
for(int i=idx+1; i<num; i++) {
if(now+a[i]>m){
return ;
}
ans[cnt++]=a[i];
dfs(now+a[i],i);
cnt--;
}
}
int main() {
scanf("%d%d",&n,&m);
int sum=0;
while(n--) {
scanf("%d",&t);
sum+=t;//计算总和
if(t<=m) a[num++]=t;//单独大于m的肯定用不上,虽然后面测试证明,不这样做也能过。。。
}
if(sum<m){
cout<<"No Solution";
return 0;
}
sort(a,a+num);
dfs(0,-1);
cout<<"No Solution";
return 0;
}
完结撒花!