坑爹的黑店

坑爹的黑店

题目信息

题目备注

  • 题目出处:程序设计大赛
  • 题目类型:数学推导

题目描述

今天小明去了一个风景如画的地方散心,但是自己带的饮料喝完了,小明口渴难耐,见不远处有家小商店,于是跑去买饮料。

小明:“我要买饮料!”

店主:“我们这里有三种饮料,矿泉水1.5元一瓶,可乐2元一瓶,橙汁3.5元一瓶。”

小明:“好的,给我一瓶矿泉水。”

说完他掏出一张N元的大钞递给店主。

店主:“我忘了提醒你了,我们这里没有找客人钱的习惯的,多的钱我们都当小费收了的,嘿嘿。”

小明:“......”

小明环顾四周,就这一家商店,况且实在太渴了,就决定在这买了。不过小明想,与其把钱当小费送给他还不如自己多买一点饮料,反正早晚都要喝,但是要尽量少让他赚小费。

现在小明希望你能帮他计算一下,最少他要给店主多少小费。

输入

输入数据的第一行是一个整数T(1<=T<=100),代表测试数据的数量。然后是T行测试数据,每个测试数据只包含一个正整数N(1<=N<=10000),N代表小明手中钞票的面值,以分为单位。
注意:商店里只有题中描述的三种饮料。

输出

对于每组测试数据,请你输出小明最少要浪费多少钱给店主作为小费,以分为单位。

样例输入

2
900
250

样例输出

0
50

题解

我的题解

1.初步分析

  这道题我在刚拿到手的时候认为这是一道贪心算法,因此使用贪心算法做了一遍,结果做不出来,因为这道题实际上并不是一道贪心算法题,而是一道数学分析题。现在我们来从头到尾重新分析这道题。

  有三种饮料,分别为1.5元,2元,3.5元,我们有n元钱来买饮料,因为剩下的钱就是小费,因此我们要尽量剩下更少的钱。这道题乍一看确实非常具备成为贪心的潜质,但经过仔细思考之后,我们其实可以发现这道题实际上不是贪心,因为在这道题中,体现出的中心思想是:我们如何让这三个价格组合,进而使它们的价格加和更加接近我们手里的钱,这里侧重的是组合,而且在这道题中饮料并没有上限,这代表我们可以贪欲无穷,在实际生活中,贪欲是由于缺乏导致的,而当我们不缺乏东西,我们的资源是无限的时候,贪欲自然变得没有意义。而在算法题中,当我们选择的东西资源是无限的时候,贪心算法往往需要谨慎的使用,在这里我们就不能使用贪心算法,就和现实生活中一样,我们缺乏的时候往往会由着自己的性子贪婪的能多拿一点算一点;而当我们的资源无限了,我们就需要小心谨慎的分析了,我们应该拿什么,怎么拿,拿多少才对我有好处

  在这道题中我一开始的思路就是贪心算法,我的思路是先疯狂的拿最贵的,然后剩下的钱买不起最贵的之后,就开始拿第二贵的,知道最后的钱连最便宜的饮料也买不起之后,方可结束,这个思路乍一听是没问题的,但是在这个问题中确是错误的:当我们有4块5的时候,我们首先买一瓶3块5的橙汁,还剩下1块钱,然后这1块钱就什么也买不起了,给店家消费1块钱;然而事实是我们可以直接买三平1块5的水,一分小费也不给。使用我这种算法就解决不出来。当我测到这里的时候,我开始怀疑我贪心的方法不对了,诚然我贪心的方法确实不对,因为此时我贪心的不是省钱,而是尽量获得更多昂贵的饮料,这并没有从省钱上考虑,因此我在此时直接换了方向。我下一个贪心的方向换成了我要尽量剩下更少的钱,而我们如何剩下更少的钱呢?我们可以先计算一下我们一直买第一种饮料,一直买第二种饮料,一直买第三种饮料三种情况下剩下的钱最少的情况,然后选择剩下钱最少的那种情况作为第二轮购买的初始金额,这样一来我就能找到最少的策略。这个思路听上去更加靠谱了,但是其时间复杂度也增加了不少,而且,其也能找到错误样例。当我们有6块5的时候,使用我的算法首先会尝试一直购买1块5的饮料的策略,这时会剩下0.5块钱,在尝试只买2块的饮料的时候也会剩下0.5块钱,购买第三种饮料的时候时剩下3块钱,这个策略就会直接被淘汰,然后使用剩下0.5块钱的策略继续计算,然而0.5块钱啥也买不了,因此就留作小费了,但实际上,我们有一个3.5+1.5+1.5 = 6.5的策略不剩下任何小费,但是这个策略就被我忽略了。

  最后我的贪心算法宣布破产,而我最后我也给出了一个最终方案,就是使用递归继续回溯式的策略枚举,这个方法应该可行,但是由于时间复杂度我就没有尝试,以后留作大家的谈资吧。

2.正式分析

  在初步分析基本上错误之后我逐渐得到了正确的结论,这个题的中心思想是我如何得到一个能够让我剩下的钱更少的策略,而不是通过贪心一直得到什么。我们可以将这个问题抽象成一个更加简单的数学模型:用几个数字拼凑成一个更加接近某数的大数。在这个过程中我们会经常用到取模的思想,我们从简单方法类推:当我们想让一个数字通过自身的叠加,进而接近另一个数字的时候,我们往往会使用取模的方式得到叠加次数;当这个数字变成了两个,问题就变得复杂化了,但是我们仍然可以使用数学分析来解决这个问题,我们举一个例子,我们想通过叠加4和6,来接近25,我们如何叠加?我们首先找到4和6的最大公因数,是2,然后我们让4和6都除2,为2和3,2和3两个数字组合,实际上可以组成任何大于3的数字,这是一个神奇的数学规律:当一个奇数和一个偶数进行组合时,可以组合成任何大于最小的数字的数字。而现在我们要为它们的组合乘2,这意味着这两个数字可以组成任何一个大于4的偶数,换言之,当我们手持一个大于4的数字的时候,只要对2求余,剩下的数字就是它们两个组合距离我们手持数字的最短距离。

  现在我们将这个数学规律进行推广,对于两个以上的数字,我们找到它们的最大约数A,只要这三个数字除以A有奇数有偶数,我们就可以用这三个数字组合出任意大于这三个数字中最大的A的倍数(保守情况),当这三个树在除以A相邻的时候,我们可以组合出任意大于这三个数字中最小的A的倍数,而我们想要求一个数字距离这三个数字最近的距离时,直接对A求余即可。

  因此在这个问题中,我们的解法为,通过计算得到三个价格的最大公约数(50),然后我们发现在除以50之后,三个数字并不相邻:3,4,7,但是需要注意的是两个3可以组成6,因此最大的可组合下限是6*50也就是300,因此我们可以得到结论:当我们手持数字大于300的时候,我们可以直接对50进行求余,得到的结果就是三数组合距离我们手持数字的最近距离。而小于这个数字的时候我们需要另行分析。

  基于这个数学思想,我们最终可以得到这样的一个代码:

import java.util.Scanner;
 
public class Main{
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner scanner=new Scanner(System.in);
        int T=scanner.nextInt();
        int num=0;
        for(int i=0;i<T;i++){
            int N=scanner.nextInt();
            if(N<150)
                System.out.println(N);
            else if(N>=300)
                System.out.println(N%50);
            else if(N==150||N==200)
                System.out.println(0);
            else if(150<N&&N<200)
                System.out.println(N%150);
            else
                System.out.println(N-200);
        }
    }
 
}

  上文中的代码是别人写的,以我的思路,还可以将150和200划分到一起,把250这个情况单独拿出来分析也就是:

if(N<150){
	System.out.println(N);
}else if(N<=200){
    System.out.println(N%50);
}else if(N<300){
    System.out.println(N-200);
}else{
    System.out.println(N%50);
}

3.最终分析

  由于发现了一个疑似数学规律的东西,我和另外一个朋友就此规律进行了比较深入的讨论,我们发现这个规律并不是完全正确的,我们发现我们不能使用最大公约树求所有数字组合的最小距离,实际上我们可以把这个数学规律拆成:两个数字的组合相加,在超过一定限度之后会变得步长为1连续,我们发现了几个数字的组合有这种特性:1和任意数字,2和3,3和4,2和7,3和5,其余则不可,我们发现这些数字都有一个特征:最多有一个偶数,必须都为质数,相加不超过9,除1是特殊情况以外其他的数对都符合这个概念。

  需要注意的是这道题还可以使用枚举思想基于动态规划做出来,现在先不讨论这个算法,以后有时间在研究这个动态规划的算法。

其他题解

  目前没有学习到其他更加有价值或者更加优秀的解题方法。

posted @ 2022-07-25 08:09  云杉木屋  阅读(68)  评论(0编辑  收藏  举报