[SSPU蓝桥杯预选]T5:凑零钱

5 凑零钱 (25 分)
韩梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有10^4枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。

输入格式:
输入第一行给出两个正整数:N(≤10^​4​​ )是硬币的总个数,M(≤10^2​​ )是韩梅梅要付的款额。第二行给出 N 枚硬币的正整数面值。数字间以空格分隔。

输出格式:
在一行中输出硬币的面值 V​1≤V​2≤⋯≤V​k,满足条件 V​1+V​2+…+V​k=M。数字间以 1 个空格分隔,行首尾不得有多余空格。若解不唯一,则输出最小序列。若无解,则输出 No Solution

注:我们说序列{ A[1],A[2],⋯ }比{ B[1],B[2],⋯ }“小”,是指存在 k≥1 使得 A[i]=B[i] 对所有 i

输入样例 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

题解:
拿到题目,第一反应觉得是一道要求正好装满的01背包题目,花了半个小时写了一个dp,用一个set存dp[m]所用到的硬币的面值,输出的时候发现答案不对,答案是135,我的程序跑出来是234。

因为我太弱了,只刷过几个经典的背包模板,没怎么遇到过需要回溯dp来源的题目,发现如果坚持用dp可能这道题只能爆零了,所以临时选择暴力骗分。

先把所有输入的硬币面值存在数组里sort一下,用STL的全排列函数枚举所有的顺序,每次枚举从头开始求和,直到sum>=m时结束,sum第一次与m相等时得到的顺序中被sum加过的元素即为字典序最小的答案。

写完暴力交上去意料之内TLE了几个点,突然想起来之前在洛谷上做过一道题的题解中给了一种针对全排列的顺序优化方法。
P1118 [USACO06FEB]数字三角形

当得出前i项和sum>m时,后面紧跟着的一些枚举是没必要的,它们的前i项和会比此时的sum更大,此时我们可以将第i项到第n项直接从大到小排序,相当于跳过了这一段没有作用的枚举,大大优化了程序的时间复杂度。
(其实我不会算O,但我知道优化后肯定会快很多)

例如,输入的m是7,硬币的面值是1-9,那么当我们得到一个顺序为:

1 2 3 4 5 6 7 8 9

“前3项和为6”,“前4项和为10”,当顺序按照字典序逐渐变大时,较大的数会慢慢往前浮,并且靠后的数会优先变化,许多次变化之后才会波及到前面的数,所以接下来每一次判断的结果都是“前3项和为6”,“前4项和为10”。

比如,上面的序列进行一次next_permutation后就变成了以下内容,前7项完全一样,只有8和9换了位置:

1 2 3 4 5 6 7 9 8

于是,当前4项的和sum>m时,我们可以用sort(a+4,a+9,greater());来让从i开始的顺序从大到小排序:

1 2 3 9 8 7 6 5 4

此时再进行一次next_permutation后,无用的枚举将会直接全部跳过:

1 2 4 3 5 6 7 8 9

前3项和为7!此时得到了想要的答案,直接break循环,记录此时sum最后加到了第i个元素,再输出a[1]至a[i]即可,代码如下:

#include
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	int a[n+1],ans[n+1];
	ans[0]=0;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	sort(a+1,a+n+1);
	do
	{
		int sumn=0;
		for(int i=1;i<=n;i++)
		{
			sumn+=a[i];
			if(sumn==m)
			{
				for(int j=1;j<=i;j++)
				if(j==i)
				cout<<a[j];
				else
				cout<<a[j]<<" ";
				return 0;
			}
			if(sumn>m)
			{
				sort(a+i,a+n+1,greater<int>());
				break;
			}
		}
	}
	while(next_permutation(a+1,a+n+1));
	cout<<"No Solution";
}

PS:考试的时候因为优化顺序的那个sort忘记加比较函数了,导致优化用的sort是从小到大排,调试的时候每次调用全排列函数后出来的顺序居然是一样的,一度让我以为机房的编译器坏掉了(逃)。

你可能感兴趣的:([SSPU蓝桥杯预选]T5:凑零钱)