Grothendick最为人所知的是他思考的时候几乎不依赖具体的例子。他的思考方式是如此的独特。他的思考方式来自于他个人的境遇,不是所有人都可以模仿的,然而不被具体的例子蒙蔽,看到整个局势对我们来说依然是十分重要的。
不依赖具体的例子就说明我们要研究事物对象的属性、性质和它于其他对象的关系。
你可以这样认为——和依赖句子一样,这样是为了增加我们所知道的信息,以使得我们能够用这些信息来解题。
[蓝桥杯 2017 省 AB] 分巧克力
题目描述
儿童节那天有 K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 * 的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:
1. 形状是正方形,边长是整数。
2. 大小相同。
例如一块 6 * 5 的巧克力可以切出 6块 2 * 2 的巧克力或者 2 块 3 * 3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小 计算出最大的边长是多少么?
## 输入格式
第一行包含两个整数 N 和 K。(1 <= N,K <= 10^5)。
以下 N行每行包含两个整数 和 。(1 <= , <= 10^5)。
输入保证每位小朋友至少能获得一块 1 * 1的巧克力。
输出格式
输出切出的正方形巧克力最大可能的边长。
样例
样例输入
2 10
6 5
5 6样例输出
2
package 练习;
import java.util.*;
public class 测试 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String[] s = scan.nextLine().split(" ");
int N = Integer.parseInt(s[0]);
int K = Integer.parseInt(s[1]);
long[] H = new long[N + 1]; // 可以有剩余的
long[] W = new long[N + 1];
long[] minLengthEach = new long[N + 1];
long[] maxLengthEach = new long[N + 1];
long minSquare = 0;
long minLength = 0;
long maxLength = 0;
long AmaxLength = 0;
long number = 0;
long min = 0;
for (int i = 1; i <= N; i++) {
String[] m = scan.nextLine().split(" ");
H[i] = Integer.parseInt(m[0]);
W[i] = Integer.parseInt(m[1]);
long larger = Math.max(H[i], W[i]);
long smaller = Math.min(H[i], W[i]);
minLengthEach[i] = smaller ;
maxLengthEach[i] = larger ;
if ( i == 1 || gcd( smaller , gcd(maxLengthEach[i], minLengthEach[i]) ) < minSquare ) {
if(smaller > gcd(maxLengthEach[i - 1], minLengthEach[i - 1]) )
minSquare = gcd( smaller , gcd(maxLengthEach[i - 1], minLengthEach[i - 1]) );
else
minSquare = gcd( smaller , gcd( minLengthEach[i - 1] , maxLengthEach[i - 1]) );
}
if ( i == 1 || smaller < minLength )
minLength = smaller ;
if ( i == 1 || larger > maxLength )
maxLength = larger ;
}
int j = 1;
while( j * minSquare <= minLength) { //j * minSquare < maxLength
j ++; // ???
}
AmaxLength = j * minSquare ;
System.out.println( check( minLength , maxLength , AmaxLength , N , K , minLengthEach , maxLengthEach) );
}
public static long gcd(long x, long y) {
return y == 0 ? x : gcd(y, x % y);
}
public static long check( long minLength , long maxLength ,long AmaxLength , int N , int K , long[] minLengthEach , long[] maxLengthEach) {
long number = 0;
for(int i = 1; i <= N; i ++)
number = number + ( minLengthEach[i] / AmaxLength ) * ( maxLengthEach[i] / AmaxLength ) ;
if(number >= K)
return AmaxLength ;
else
return (check( minLength , maxLength , AmaxLength - 1 , N , K , minLengthEach , maxLengthEach));
}
}
int j = 1;
while( j * minSquare <= minLength) { //j * minSquare < maxLength
j ++; // ???
}
这个循环的判断只能使得部分输入成立。
int j = 1;
while( j * minSquare < maxLength ) {
j ++;
}
这个才能全部成立。(效率使得最后一个还是过不去)
乍一看第一个没有什么问题,我们求得了所有巧克力最小的边长的最大公约数,然后通过第一个while循环来找到所有巧克力里面最小边所能被切割的最大边数(最大公约数是用这个长度的正方形能把所有的巧克力切割成一模一样的小部分),这样做可以从最大的切割边开始测试能不能让所有的小朋友都吃到,而且不用去管最大公约数以下的长度(应为我的check方法里会检测minSquare - 1,如果不满足要求就会继续 减 1,而且只要用 if (number > = K)进行判断就可以了)。
样例
样例输入
2 10
6 5
5 6样例输出
2
这是依据这个例子想到的,你去试试看,。我很自然的就顺着这个思路走下去就像,把每一块都切出来一部分就可以了,它会有多出来的,而且可以“浪费掉”,一些只要满足最大和每个人都有即可。这意味着我们不知道到底多大可以,只有“整数边”这个条件加以限制,因此无穷列举成立唯一方案。我的第一想法就是每都块巧克力都切出来一点。这是对的,我输入例子的样例输入,得到了相同的结果。
但是那个关键的线索从我的眼皮子底下偷偷溜走了
而且可以“浪费掉”,一些只要满足最大和每个人都有即可。我的第一想法就是每都块巧克力都切出来一点。(可以浪费的范围)
浪费的巧克力可以是一整块。土豪可以有K个大巧克力加上小巧克力,一人一块大的,其他丢掉(包括minLength那块),还有就是把一块整切出的最大正方形巧克力(其minLengthEach[i])当作一个单位进行切割。
整体、局部是数学里面的两个重要的角度
我们应该借鉴学习。
这里忽略掉了另外一些情况的原因是我没有对
j * minSquare
进行分析——它作为变量的属性——它的取值范围(依据具体情况)。只是不加分析的就顺着某个思路前进了。事实上那些实例就是——局部的思维——在此之前应该先对其进行整体分析,并一个个排除。哪些范围是一定要去掉的,理由是什么,不是必要不要去掉。用语言的描述就是在你顺着具体例子的时候你要考虑你一开始的时候想到的是对的吗。
看起来显然是显然的
但是应该考虑到E不是空集。因为不依赖具体的例子要对对象的数学属性进行分析。而集合的属性就那几个,排除几个后就是——空与非空。
这是题目的来源(思考数学不依赖具体的例子! 伟大的格罗滕迪克 BabyRudin习题1.4 有序集/界_哔哩哔哩_bilibili)
值得注意的是,我们不要极端,不要丢弃例子了,只是要在次基础上还要先考察过整体后再深入局部。Grothendick晚年申请CNRS职位的报告中他甚至认为自己这从种“刻意追求最大普遍性的对象”(原文翻译)的思想中重新回归了关注单纯具体对象的状态。他从Deligine证明韦伊猜想用的是另外一个数学家的思路(显然那个数学家不是不依赖具体的例子(格的抽象无人能及),Deligine在阿贝尔奖得主采访里说格的方法反而是种阻碍,它使得人们只会往一个方向思考),没有完成自己的愿景(格皇的框架就是为了解决韦伊猜想而建立的)的事情之中获得启示。
这道编程题的最佳解决方法其实就是暴力算法加上减枝即可。